From 67f44805b41a532b7b440773f7ee6d17dc0e97f4 Mon Sep 17 00:00:00 2001 From: Ori Bernstein Date: Thu, 3 Sep 2015 13:06:18 -0700 Subject: Openbmc dev snapshot. --- .../recipes-wedge/rackmon/rackmon/Makefile | 34 +- .../recipes-wedge/rackmon/rackmon/hexfile.py | 174 ++++++ .../recipes-wedge/rackmon/rackmon/modbus.c | 181 +++++- .../recipes-wedge/rackmon/rackmon/modbus.h | 47 +- .../recipes-wedge/rackmon/rackmon/modbuscmd.c | 152 ++--- .../recipes-wedge/rackmon/rackmon/modbussim.c | 2 - .../rackmon/rackmon/psu-update-delta.py | 269 +++++++++ .../rackmon/rackmon/rackmon-config.py | 93 +++ .../recipes-wedge/rackmon/rackmon/rackmond.c | 637 +++++++++++++++++++++ .../recipes-wedge/rackmon/rackmon/rackmond.h | 46 ++ .../recipes-wedge/rackmon/rackmon/rackmondata.c | 58 ++ .../rackmon/rackmon/setup-rackmond.sh | 20 + .../recipes-wedge/rackmon/rackmon_0.1.bb | 33 +- 13 files changed, 1636 insertions(+), 110 deletions(-) create mode 100644 meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/hexfile.py create mode 100755 meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/psu-update-delta.py create mode 100644 meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmon-config.py create mode 100644 meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmond.c create mode 100644 meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmond.h create mode 100644 meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmondata.c create mode 100644 meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/setup-rackmond.sh (limited to 'meta-facebook/meta-wedge/recipes-wedge/rackmon') diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/Makefile b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/Makefile index 4a3c25d..926bf52 100644 --- a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/Makefile +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/Makefile @@ -1,16 +1,40 @@ # Copyright 2014-present Facebook. All Rights Reserved. -all: modbuscmd gpiowatch modbussim +# +# This program file 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; version 2 of the License. +# +# 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 in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA + +override CFLAGS+=-D_GNU_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=199309 -Wall -Werror -std=c99 +override LDFLAGS+=-pthread +all: modbuscmd gpiowatch modbussim rackmond rackmondata + +rackmondata: rackmondata.c modbus.c + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +rackmond: rackmond.c modbus.c + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) modbuscmd: modbuscmd.c modbus.c - $(CC) -D_BSD_SOURCE -Wall -Werror -std=c99 -o $@ $^ $(LDFLAGS) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) modbussim: modbussim.c modbus.c - $(CC) -D_BSD_SOURCE -Wall -Werror -std=c99 -o $@ $^ $(LDFLAGS) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) gpiowatch: gpiowatch.c - $(CC) -D_BSD_SOURCE -Wall -Werror -std=c99 -o $@ $^ $(LDFLAGS) + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) .PHONY: clean clean: - rm -rf *.o modbuscmd gpiowatch modbussim + rm -rf *.o modbuscmd gpiowatch modbussim rackmond rackmondata diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/hexfile.py b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/hexfile.py new file mode 100644 index 0000000..c484fdf --- /dev/null +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/hexfile.py @@ -0,0 +1,174 @@ +# The MIT License (MIT) +# ===================== +# +# Copyright (c) 2014 Ryan Sturmer +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# https://github.com/ryansturmer/hexfile/blob/master/hexfile/core.py + +import itertools + +def short(msb,lsb): + return (msb<<8) | lsb + +class HexFile(object): + def __init__(self, segments): + self.segments = segments + + def __getitem__(self, val): + if isinstance(val, slice): + address = val.start + else: + address = val + + for segment in self.segments: + if address in segment: + return segment[val] + + raise IndexError('No segment contains address 0x%x' % address) + + def __len__(self): + return sum(map(len, self.segments)) + + @property + def size(self): + return len(self) + + def __iter__(self): + return itertools.chain(*self.segments) + + @staticmethod + def load(filename): + segments = [Segment(0)] + + with open(filename) as fp: + lines = fp.readlines() + + extended_linear_address = 0 + current_address = 0 + end_of_file = False + + lineno = 0 + for line in lines: + lineno += 1 + line = line.strip(); + if not line.startswith(':'): + continue + + if end_of_file: + raise Exception("Record found after end of file on line %d" % lineno) + + bytes = [int(line[i:i+2], 16) for i in range(1,len(line), 2)] + byte_count = bytes[0] + address = short(*bytes[1:3]) + record_type = bytes[3] + checksum = bytes[-1] + data = bytes[4:-1] + computed_checksum = ((1 << 8)-(sum(bytes[:-1]) & 0xff)) & 0xff + + if(computed_checksum != checksum): + raise Exception("Record checksum doesn't match on line %d" % lineno) + + if record_type == 0: + if byte_count == len(data): + current_address = (address | extended_linear_address) + have_segment = False + for segment in segments: + if segment.end_address == current_address: + segment.data.extend(data) + have_segment = True + break + if not have_segment: + segments.append(Segment(current_address, data)) + else: + raise Exception("Data record reported size does not match actual size on line %d" % lineno) + elif record_type == 1: + end_of_file = True + elif record_type == 4: + if byte_count != 2 or len(data) != 2: + raise Exception("Byte count misreported in extended linear address record on line %d" % lineno) + extended_linear_address = short(*data) << 16 + + else: + raise Exception("Unknown record type: %s" % record_type) + return HexFile(segments) + + def pretty_string(self, stride=16): + retval = [] + for segment in self.segments: + retval.append('Segment @ 0x%08x (%d bytes)' % (segment.start_address, segment.size)) + retval.append(segment.pretty_string(stride=stride)) + retval.append('') + return '\n'.join(retval) + +def load(filename): + return HexFile.load(filename) + +class Segment(object): + def __init__(self, start_address, data = None): + self.start_address = start_address + self.data = data or [] + + def pretty_string(self, stride=16): + retval = [] + addresses = self.addresses + ranges = [addresses[i:i+stride] for i in range(0, self.size, stride)] + for r in ranges: + retval.append('%08x ' % r[0] + ' '.join(['%02x' % self[addr] for addr in r])) + return '\n'.join(retval) + + def __str__(self): + return '<%d byte segment @ 0x%08x>' % (self.size, self.start_address) + def __repr__(self): + return str(self) + + @property + def end_address(self): + return self.start_address + len(self.data) + + @property + def size(self): + return len(self.data) + + def __contains__(self, address): + return address >= self.start_address and address < self.end_address + + def __getitem__(self, address): + if isinstance(address, slice): + if address.start not in self or address.stop-1 not in self: + raise IndexError('Address out of range for this segment') + else: + d = self.data[address.start-self.start_address:address.stop-self.start_address:address.step] + start_address = address.start + self.start_address + return Segment(start_address, d) + else: + if not address in self: + raise IndexError("Address 0x%x is not in this segment" % address) + return self.data[address-self.start_address] + + @property + def addresses(self): + return range(self.start_address, self.end_address) + + def __len__(self): + return len(self.data) + + def __iter__(self): + return iter(self.data) diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbus.c b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbus.c index 46618a9..8fb0835 100644 --- a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbus.c +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbus.c @@ -16,6 +16,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include "modbus.h" #include #include @@ -26,9 +27,15 @@ #include #include #include +#include +#include -static int loops = 0; -void waitfd(int fd) { +int verbose = 0; + +#define TIOCSERWAITTEMT 0x5499 +int waitfd(int fd) { + int loops = 0; + ioctl(fd, TIOCSERWAITTEMT, DEFAULT_GPIO); while(1) { int lsr; int ret = ioctl(fd, TIOCSERGETLSR, &lsr); @@ -37,8 +44,10 @@ void waitfd(int fd) { break; } if(lsr & TIOCSER_TEMT) break; + // never should hit this with new ioctl loops++; } + return loops; } void gpio_on(int fd) { @@ -60,8 +69,7 @@ void decode_hex_in_place(char* buf, size_t* len) { void append_modbus_crc16(char* buf, size_t* len) { uint16_t crc = modbus_crc16(buf, *len); - if (verbose) - fprintf(stderr, "[*] Append Modbus CRC16 %04x\n", crc); + dbg("[*] Append Modbus CRC16 %04x\n", crc); buf[(*len)++] = crc >> 8; buf[(*len)++] = crc & 0x00FF; } @@ -78,7 +86,7 @@ size_t read_wait(int fd, char* dst, size_t maxlen, int mdelay_us) { size_t read_size = 0; size_t pos = 0; memset(dst, 0, maxlen); - for(;;) { + while(pos < maxlen) { FD_ZERO(&fdset); FD_SET(fd, &fdset); timeout.tv_sec = 0; @@ -95,10 +103,11 @@ size_t read_wait(int fd, char* dst, size_t maxlen, int mdelay_us) { fprintf(stderr, "read error: %s\n", strerror(errno)); exit(1); } - if((pos + read_size) < maxlen) { + if((pos + read_size) <= maxlen) { memcpy(dst + pos, read_buf, read_size); pos += read_size; } else { + return pos; fprintf(stderr, "Response buffer overflowed!\n"); } } @@ -181,3 +190,163 @@ uint16_t modbus_crc16(char* buffer, size_t buffer_length) { return (crc_hi << 8 | crc_lo); } + + +double ts_diff (struct timespec* begin, struct timespec* end) { + return 1000.0 * (end->tv_sec) + (1e-6 * end->tv_nsec) + - (1000.0 * (begin->tv_sec) + (1e-6 * begin->tv_nsec)); +} + +static long success = 0; +static long crcfail = 0; +static long timeout = 0; +static long stat_wait = 0; + +int modbuscmd(modbus_req *req) { + int error = 0; + struct termios tio; + char modbus_cmd[req->cmd_len + 2]; + size_t cmd_len = req->cmd_len; + + if (verbose) + fprintf(stderr, "[*] Setting TTY flags!\n"); + memset(&tio, 0, sizeof(tio)); + // CREAD should be left *off* until we've confirmed THRE + // to avoid catching false character starts + cfsetspeed(&tio,B19200); + tio.c_cflag |= PARENB; + tio.c_cflag |= CLOCAL; + tio.c_cflag |= CS8; + tio.c_iflag |= INPCK; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + CHECK(tcsetattr(req->tty_fd,TCSANOW,&tio)); + + memcpy(modbus_cmd, req->modbus_cmd, cmd_len); + append_modbus_crc16(modbus_cmd, &cmd_len); + + // print command as sent + if (verbose) { + fprintf(stderr, "Will send: "); + print_hex(stderr, modbus_cmd, cmd_len); + fprintf(stderr, "\n"); + } + + dbg("[*] Writing!\n"); + + // hoped adding the ioctl to do the switching would have alleviated the + // need to do SCHED_FIFO, but we still get preempted between the write and + // ioctl syscalls w/o it often enough to break f/w updates. + struct sched_param sp; + sp.sched_priority = 50; + int policy = SCHED_FIFO; + CHECKP(sched, pthread_setschedparam(pthread_self(), policy, &sp)); + // gpio on, write, wait, gpio off + gpio_on(req->gpio_fd); + struct timespec write_begin; + struct timespec wait_begin; + struct timespec wait_end; + struct timespec read_end; + clock_gettime(CLOCK_MONOTONIC_RAW, &write_begin); + write(req->tty_fd, modbus_cmd, cmd_len); + clock_gettime(CLOCK_MONOTONIC_RAW, &wait_begin); + int waitloops = waitfd(req->tty_fd); + clock_gettime(CLOCK_MONOTONIC_RAW, &wait_end); + gpio_off(req->gpio_fd); + sp.sched_priority = 0; + // Enable UART read + tio.c_cflag |= CREAD; + CHECK(tcsetattr(req->tty_fd,TCSANOW,&tio)); + policy = SCHED_OTHER; + CHECKP(sched, pthread_setschedparam(pthread_self(), policy, &sp)); + + dbg("[*] waitfd loops: %d\n", waitloops); + dbg("[*] reading any response...\n"); + // Read back response + size_t mb_pos = 0; + memset(req->dest_buf, 0, req->dest_limit); + if(req->expected_len == 0) { + req->expected_len = req->dest_limit; + } + if(req->expected_len > req->dest_limit) { + return -1; + } + mb_pos = read_wait(req->tty_fd, req->dest_buf, req->expected_len, req->timeout); + clock_gettime(CLOCK_MONOTONIC_RAW, &read_end); + req->dest_len = mb_pos; + if(mb_pos >= 4) { + uint16_t crc = modbus_crc16(req->dest_buf, mb_pos - 2); + dbg("Modbus response CRC: %04X\n ", crc); + if((req->dest_buf[mb_pos - 2] == (crc >> 8)) && + (req->dest_buf[mb_pos - 1] == (crc & 0x00FF))) { + dbg("CRC OK!\n"); + } else { + dbg("BAD CRC :(\n"); + fprintf(stderr, "bad crc timings:"); + fprintf(stderr, " write: %.2f ms", ts_diff(&write_begin, &wait_begin)); + fprintf(stderr, " wait: %.2f ms", ts_diff(&wait_begin, &wait_end)); + fprintf(stderr, " read: %.2f ms\n", ts_diff(&wait_end, &read_end)); + if(!req->scan) { + crcfail++; + } + if(verbose) { + print_hex(stderr, req->dest_buf, mb_pos); + } + return MODBUS_BAD_CRC; + } + } else { + fprintf(stderr, "timeout timings:"); + fprintf(stderr, " write: %.2f ms", ts_diff(&write_begin, &wait_begin)); + fprintf(stderr, " wait: %.2f ms", ts_diff(&wait_begin, &wait_end)); + fprintf(stderr, " wait: %d iters", waitloops); + fprintf(stderr, " read: %.2f ms\n", ts_diff(&wait_end, &read_end)); + dbg("No response :(\n"); + if(!req->scan) { + timeout++; + } + return MODBUS_RESPONSE_TIMEOUT; + } + +cleanup: + if(error != 0) { + error = -1; + fprintf(stderr, "%s\n", strerror(errno)); + } else { + //fprintf(stderr, "success, timings:"); + //fprintf(stderr, " write: %.2f ms", ts_diff(&write_begin, &wait_begin)); + //fprintf(stderr, " wait: %.2f ms", ts_diff(&wait_begin, &wait_end)); + //fprintf(stderr, " read: %.2f ms -- ", ts_diff(&wait_end, &read_end)); + if(stat_wait == 0 && !req->scan) { + fprintf(stderr, "success: %.2f%% crcfail %.2f%%, timeout %.2f%%\n", + ((double) 100.0 * success / (success + crcfail + timeout)), + ((double) 100.0 * crcfail / (success + crcfail + timeout)), + ((double) 100.0 * timeout / (success + crcfail + timeout))); + stat_wait = 1000; + fprintf(stderr, "success timings:"); + fprintf(stderr, " write: %.2f ms", ts_diff(&write_begin, &wait_begin)); + fprintf(stderr, " wait: %.2f ms", ts_diff(&wait_begin, &wait_end)); + fprintf(stderr, " wait: %d iters", waitloops); + fprintf(stderr, " read: %.2f ms\n", ts_diff(&wait_end, &read_end)); + } else if (!req->scan) { + stat_wait--; + } + if(!req->scan) { + success++; + } + } + return 0; +} + +const char* modbus_strerror(int mb_err) { + if (mb_err < 0) { + mb_err = -mb_err; + } + switch(mb_err) { + case 4: + return "timed out"; + case 5: + return "crc check failed"; + default: + return "unknown"; + } +} diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbus.h b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbus.h index 435d518..1354feb 100644 --- a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbus.h +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbus.h @@ -26,12 +26,27 @@ uint16_t modbus_crc16(char* buffer, size_t length); #define DEFAULT_TTY "/dev/ttyS3" #define DEFAULT_GPIO 45 -#define CHECK(x) { if((x) < 0) { \ - error = x; \ +extern int verbose; +#define dbg(...) if(verbose) { fprintf(stderr, __VA_ARGS__); } +#define log(...) { fprintf(stderr, __VA_ARGS__); } + +#define CHECK(expr) { int _check = expr; if((_check) < 0) { \ + error = _check; \ + goto cleanup; \ +} } +#define CHECKP(name, expr) { int _check = expr; if((_check) < 0) { \ + error = _check; \ + perror(#name); \ goto cleanup; \ } } +#define BAIL(...) { \ + fprintf(stderr, __VA_ARGS__); \ + fflush(stderr); \ + error = -1; \ + goto cleanup; \ +} -void waitfd(int fd); +int waitfd(int fd); void gpio_on(int fd); void gpio_off(int fd); void decode_hex_in_place(char* buf, size_t* len); @@ -41,6 +56,30 @@ void print_hex(FILE* f, char* buf, size_t len); // Read until maxlen bytes or no bytes in mdelay_us microseconds size_t read_wait(int fd, char* dst, size_t maxlen, int mdelay_us); -extern int verbose; + +typedef struct _modbus_req { + int tty_fd; + int gpio_fd; + const char *modbus_cmd; + size_t cmd_len; + int timeout; + size_t expected_len; + char *dest_buf; + size_t dest_limit; + size_t dest_len; + int scan; +} modbus_req; + +int modbuscmd(modbus_req *req); +// Modbus errors + +#define MODBUS_RESPONSE_TIMEOUT -4 +#define MODBUS_BAD_CRC -5 + +const char* modbus_strerror(int mb_err); + +// Modbus constants +#define MODBUS_READ_HOLDING_REGISTERS 3 + #endif diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbuscmd.c b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbuscmd.c index 2d33039..78f62a1 100644 --- a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbuscmd.c +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbuscmd.c @@ -16,51 +16,54 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -#include -#include +#include #include -#include +#include +#include #include #include +#include #include -#include +#include #include -#include +#include +#include +#include #include "modbus.h" - -int verbose; +#include "rackmond.h" void usage() { fprintf(stderr, - "modbuscmd [-v] [-t ] [-g ] modbus_command\n" - "\ttty defaults to %s\n" - "\tgpio defaults to %d\n" + "modbuscmd [-v] [-t ] [-x ] modbus_command\n" "\tmodbus command should be specified in hex\n" - "\teg:\ta40300000008\n", - DEFAULT_TTY, DEFAULT_GPIO); + "\teg:\ta40300000008\n" + "\tif an expected response length is provided, modbuscmd will stop receving and check crc immediately " + "after receiving that many bytes\n"); exit(1); } + int main(int argc, char **argv) { int error = 0; - int fd; - struct termios tio; - char gpio_filename[255]; - int gpio_fd = 0; - int gpio_n = DEFAULT_GPIO; - char *tty = DEFAULT_TTY; char *modbus_cmd = NULL; size_t cmd_len = 0; + int expected = 0; + uint32_t timeout = 0; verbose = 0; + rackmond_command *cmd = NULL; + char *response = NULL; + int clisock; + uint16_t response_len_actual; + struct sockaddr_un rackmond_addr; int opt; - while((opt = getopt(argc, argv, "t:g:v")) != -1) { + while((opt = getopt(argc, argv, "w:x:t:g:v")) != -1) { switch (opt) { - case 't': - tty = optarg; + case 'x': + expected = atoi(optarg); break; - case 'g': - gpio_n = atoi(optarg); + case 't': + timeout = atol(optarg); break; case 'v': verbose = 1; @@ -77,29 +80,6 @@ int main(int argc, char **argv) { usage(); } - if (verbose) - fprintf(stderr, "[*] Opening TTY\n"); - fd = open(tty, O_RDWR | O_NOCTTY); - CHECK(fd); - - if (verbose) - fprintf(stderr, "[*] Opening GPIO %d\n", gpio_n); - snprintf(gpio_filename, 255, "/sys/class/gpio/gpio%d/value", gpio_n); - gpio_fd = open(gpio_filename, O_WRONLY | O_SYNC); - CHECK(gpio_fd); - - if (verbose) - fprintf(stderr, "[*] Setting TTY flags!\n"); - memset(&tio, 0, sizeof(tio)); - cfsetspeed(&tio,B19200); - tio.c_cflag |= PARENB; - tio.c_cflag |= CLOCAL; - tio.c_cflag |= CS8; - tio.c_iflag |= INPCK; - tio.c_cc[VMIN] = 1; - tio.c_cc[VTIME] = 0; - CHECK(tcsetattr(fd,TCSANOW,&tio)); - //convert hex to bytes cmd_len = strlen(modbus_cmd); if(cmd_len < 4) { @@ -107,57 +87,45 @@ int main(int argc, char **argv) { exit(1); } decode_hex_in_place(modbus_cmd, &cmd_len); - append_modbus_crc16(modbus_cmd, &cmd_len); - // print command as sent - if (verbose) { - fprintf(stderr, "Will send: "); - print_hex(stderr, modbus_cmd, cmd_len); - fprintf(stderr, "\n"); - } - - if (verbose) - fprintf(stderr, "[*] Writing!\n"); - - // gpio on, write, wait, gpio off - gpio_on(gpio_fd); - write(fd, modbus_cmd, cmd_len); - waitfd(fd); - gpio_off(gpio_fd); + cmd = malloc(sizeof(rackmond_command) + cmd_len); + cmd->type = COMMAND_TYPE_RAW_MODBUS; + cmd->raw_modbus.length = cmd_len; + cmd->raw_modbus.custom_timeout = timeout; + memcpy(cmd->raw_modbus.data, modbus_cmd, cmd_len); + cmd->raw_modbus.expected_response_length = expected; + response = malloc(expected ? expected : 1024); + uint16_t wire_cmd_len = sizeof(rackmond_command) + cmd_len; - // Enable UART read - tio.c_cflag |= CREAD; - CHECK(tcsetattr(fd,TCSANOW,&tio)); - - if(verbose) - fprintf(stderr, "[*] reading any response...\n"); - // Read back response - char modbus_buf[255]; - size_t mb_pos = 0; - memset(modbus_buf, 0, sizeof(modbus_buf)); - mb_pos = read_wait(fd, modbus_buf, sizeof(modbus_buf), 90000); - if(mb_pos >= 4) { - uint16_t crc = modbus_crc16(modbus_buf, mb_pos - 2); - if(verbose) - fprintf(stderr, "Modbus response CRC: %04X\n ", crc); - if((modbus_buf[mb_pos - 2] == (crc >> 8)) && - (modbus_buf[mb_pos - 1] == (crc & 0x00FF))) { - if(verbose) - fprintf(stderr, "CRC OK!\n"); - print_hex(stdout, modbus_buf, mb_pos); - printf("\n"); - } else { - fprintf(stderr, "BAD CRC :(\n"); - return 5; - } - } else { - fprintf(stderr, "No response :(\n"); - return 4; + clisock = socket(AF_UNIX, SOCK_STREAM, 0); + CHECKP(socket, clisock); + rackmond_addr.sun_family = AF_UNIX; + strcpy(rackmond_addr.sun_path, "/var/run/rackmond.sock"); + int addr_len = strlen(rackmond_addr.sun_path) + sizeof(rackmond_addr.sun_family); + CHECKP(connect, connect(clisock, (struct sockaddr*) &rackmond_addr, addr_len)); + CHECKP(send, send(clisock, &wire_cmd_len, sizeof(wire_cmd_len), 0)); + CHECKP(send, send(clisock, cmd, wire_cmd_len, 0)); + CHECKP(recv, recv(clisock, &response_len_actual, sizeof(response_len_actual), 0)); + if(response_len_actual == 0) { + uint16_t errcode = 0; + CHECKP(recv, recv(clisock, &errcode, sizeof(errcode), 0)); + fprintf(stderr, "modbus error: %d (%s)\n", errcode, modbus_strerror(errcode)); + error = 1; + goto cleanup; + } + CHECKP(recv, recv(clisock, response, response_len_actual, 0)); + if(error == 0) { + printf("Response: "); + print_hex(stdout, response, response_len_actual); + printf("\n"); } - cleanup: + free(cmd); + free(response); if(error != 0) { + if(errno != 0) { + fprintf(stderr, "errno err: %s\n", strerror(errno)); + } error = 1; - fprintf(stderr, "%s\n", strerror(errno)); } return error; } diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbussim.c b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbussim.c index bf8c6c8..e276501 100644 --- a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbussim.c +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/modbussim.c @@ -28,8 +28,6 @@ #include #include "modbus.h" -int verbose = 0; - void usage() { fprintf(stderr, "modbussim [-v] [-t ] [-g ] modbus_request modbus_reply\n" diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/psu-update-delta.py b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/psu-update-delta.py new file mode 100755 index 0000000..a92cf8e --- /dev/null +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/psu-update-delta.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python +from __future__ import print_function + +import os.path +import socket +import struct +import sys +import argparse +import traceback + +import hexfile + + +def auto_int(x): + return int(x, 0) + + +parser = argparse.ArgumentParser() +parser.add_argument('--addr', type=auto_int, required=True, + help="PSU Modbus Address") +parser.add_argument('file', help="firmware file") + + +class ModbusTimeout(Exception): + pass + + +class ModbusCRCFail(Exception): + pass + + +class ModbusUnknownError(Exception): + pass + + +class BadMEIResponse(Exception): + pass + + +def rackmon_command(cmd): + srvpath = "/var/run/rackmond.sock" + replydata = [] + if os.path.exists(srvpath): + client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + client.connect(srvpath) + cmdlen = struct.pack("@H", len(cmd)) + client.send(cmdlen) + client.send(cmd) + while True: + data = client.recv(1024) + if not data: + break + replydata.append(data) + client.close() + return ''.join(replydata) + + +def pause_monitoring(): + COMMAND_TYPE_PAUSE_MONITORING = 0x04 + command = struct.pack("@Hxx", COMMAND_TYPE_PAUSE_MONITORING) + result = rackmon_command(command) + (res_n, ) = struct.unpack("@B", result) + if res_n == 1: + print("Monitoring was already paused when tried to pause") + elif res_n == 0: + print("Monitoring paused") + else: + print("Unknown response pausing monitoring: %d" % res_n) + + +def resume_monitoring(): + COMMAND_TYPE_START_MONITORING = 0x05 + command = struct.pack("@Hxx", COMMAND_TYPE_START_MONITORING) + result = rackmon_command(command) + (res_n, ) = struct.unpack("@B", result) + if res_n == 1: + print("Monitoring was already running when tried to resume") + elif res_n == 0: + print("Monitoring resumed") + else: + print("Unknown response resuming monitoring: %d" % res_n) + + +def modbuscmd(raw_cmd, expected=0, timeout=0): + COMMAND_TYPE_RAW_MODBUS = 1 + send_command = struct.pack("@HxxHHL", + COMMAND_TYPE_RAW_MODBUS, + len(raw_cmd), + expected, + timeout) + raw_cmd + result = rackmon_command(send_command) + if len(result) == 0: + raise ModbusUnknownError() + (resp_len,) = struct.unpack("@H", result[:2]) + if resp_len == 0: + (error, ) = struct.unpack("@H", result[2:4]) + if error == 4: + raise ModbusTimeout() + if error == 5: + raise ModbusCRCFail() + print("Unknown modbus error: " + str(error)) + raise ModbusUnknownError() + return result[2:resp_len] + + +def mei_command(addr, func_code, mei_type=0x64, data=None, timeout=0): + i_data = data + if i_data is None: + i_data = ("\xFF" * 7) + if len(i_data) < 7: + i_data = i_data + ("\xFF" * (7 - len(i_data))) + assert len(i_data) == 7 + command = struct.pack("BBBB", addr, 0x2b, mei_type, func_code) + i_data + return modbuscmd(command, expected=13, timeout=timeout) + + +def enter_bootloader(addr): + try: + print("Entering bootloader...") + mei_command(addr, 0xFB, timeout=4000) + except ModbusTimeout: + print("Enter bootloader timed out (expected.)") + pass + + +def mei_expect(response, addr, data_pfx, error, success_mei_type=0x71): + expected = struct.pack("BBB", addr, 0x2B, success_mei_type) + \ + data_pfx + ("\xFF" * (8 - len(data_pfx))) + if response != expected: + print(error + ", response: " + response.encode('hex')) + raise BadMEIResponse() + + +def start_programming(addr): + print("Send start programming...") + response = mei_command(addr, 0x70, timeout=10000) + mei_expect(response, addr, "\xB0", "Start programming failed") + print("Start programming succeeded.") + + +def get_challenge(addr): + print("Send get seed") + response = mei_command(addr, 0x27, timeout=3000) + expected = struct.pack("BBBB", addr, 0x2B, 0x71, 0x67) + if response[:len(expected)] != expected: + print("Bad response to get seed: " + response.encode('hex')) + raise BadMEIResponse() + challenge = response[len(expected):len(expected) + 4] + print("Got seed: " + challenge.encode('hex')) + return challenge + + +def send_key(addr, key): + print("Send key") + response = mei_command(addr, 0x28, data=key, timeout=3000) + mei_expect(response, addr, "\x68", "Start programming failed") + print("Send key successful.") + + +def delta_seccalckey(challenge): + (seed, ) = struct.unpack(">L", challenge) + for i in range(32): + if seed & 1 != 0: + seed = seed ^ 0xc758a5b6 + seed = (seed >> 1) & 0x7fffffff + seed = seed ^ 0x06854137 + return struct.pack(">L", seed) + + +def verify_flash(addr): + print("Verifying program...") + response = mei_command(addr, 0x76, timeout=60000) + mei_expect(response, addr, "\xB6", "Program verification failed") + + +def set_write_address(psu_addr, flash_addr): + # print("Set write address to " + hex(flash_addr)) + data = struct.pack(">LB", flash_addr, 0xEA) + response = mei_command(psu_addr, 0x61, data=data, timeout=3000) + mei_expect(response, psu_addr, "\xA1\xEA", "Set address failed") + + +def write_data(addr, data): + assert(len(data) == 8) + command = struct.pack(">BBB", addr, 0x2b, 0x65) + data + response = modbuscmd(command, expected=13, timeout=3000) + expected = struct.pack(">B", addr) +\ + "\x2b\x73\xf0\xaa\xff\xff\xff\xff\xff\xff" + if response != expected: + print("Bad response to writing data: " + + response.encode('hex')) + raise BadMEIResponse() + + +def send_image(addr, fwimg): + total_chunks = sum([len(s) for s in fwimg.segments]) / 8 + sent_chunks = 0 + for s in fwimg.segments: + if len(s) == 0: + continue + print("Sending " + str(s)) + set_write_address(addr, s.start_address) + for i in xrange(0, len(s), 8): + chunk = s.data[i:i+8] + if len(chunk) < 8: + chunk = chunk + ("\xFF" * (8 - len(chunk))) + sent_chunks += 1 + print("\r[%.2f%%] Sending chunk %d of %d..." % + (sent_chunks * 100.0 / total_chunks, + sent_chunks, total_chunks), end="") + sys.stdout.flush() + write_data(addr, str(bytearray(chunk))) + print("") + + +def reset_psu(addr): + print("Resetting PSU...") + try: + response = mei_command(addr, 0x72, timeout=10000) + except ModbusTimeout: + print("No reply from PSU reset (expected.)") + return + expected = struct.pack(">BBBB", addr, 0x2b, 0x71, 0xb2) +\ + ("\xFF" * 7) + if response != expected: + print("Bad response to unit reset request: " + + response.encode('hex')) + raise BadMEIResponse() + + +def erase_flash(addr): + print("Erasing flash... ") + sys.stdout.flush() + response = mei_command(addr, 0x65, timeout=30000) + expected = struct.pack(">BBBB", addr, 0x2b, 0x71, 0xa5) +\ + ("\xFF" * 7) + if response != expected: + print("Bad response to erasing flash: " + + response.encode('hex')) + raise BadMEIResponse() + + +def update_psu(addr, filename): + pause_monitoring() + fwimg = hexfile.load(filename) + enter_bootloader(addr) + start_programming(addr) + challenge = get_challenge(addr) + send_key(addr, delta_seccalckey(challenge)) + erase_flash(addr) + send_image(addr, fwimg) + verify_flash(addr) + reset_psu(addr) + + +def main(): + args = parser.parse_args() + try: + update_psu(args.addr, args.file) + except: + traceback.print_exc() + print("Firmware update failed") + resume_monitoring() + sys.exit(1) + resume_monitoring() + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmon-config.py b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmon-config.py new file mode 100644 index 0000000..e93dfae --- /dev/null +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmon-config.py @@ -0,0 +1,93 @@ +from __future__ import print_function +import struct +import socket +import os, os.path + +reglist = [ + {"begin": 0x0, #MFR_MODEL + "length": 8}, + {"begin": 0x10, #MFR_DATE + "length": 8}, + {"begin": 0x20, #FB Part # + "length": 8}, + {"begin": 0x30, #HW Revision + "length": 4}, + {"begin": 0x38, #FW Revision + "length": 4}, + {"begin": 0x40, #MFR Serial # + "length": 16}, + {"begin": 0x60, #Workorder # + "length": 4}, + {"begin": 0x68, #PSU Status + "length": 1, + "keep": 10, # 10-sample ring buffer + "flags": 1}, + {"begin": 0x69, #Battery Status + "length": 1, + "keep": 10, # 10-sample ring buffer + "flags": 1}, + {"begin": 0x80, #Input VAC + "length": 1, + "keep": 10}, + {"begin": 0x82, #Input Current AC + "length": 1, + "keep": 10}, + {"begin": 0x84, #Battery Voltage + "length": 1, + "keep": 10}, + {"begin": 0x86, #Battery Current Output + "length": 1}, + {"begin": 0x88, #Battery Current Input + "length": 1}, + {"begin": 0x8A, #Output Voltage (main converter) + "length": 1, + "keep": 10}, + {"begin": 0x8C, #Output Current (main converter) + "length": 1, + "keep": 10}, + {"begin": 0x8E, #IT Load Voltage Output + "length": 1}, + {"begin": 0x90, #IT Load Current Output + "length": 1}, + {"begin": 0x92, #Bulk Cap Voltage + "length": 1}, + {"begin": 0x94, #Input Power + "length": 1, + "keep": 10}, + {"begin": 0x96, #Output Power + "length": 1, + "keep": 10}, + {"begin": 0x98, #RPM Fan 0 + "length": 1}, + {"begin": 0x9A, #RPM Fan 1 + "length": 1}, + {"begin": 0x9E, #Temp 0 + "length": 1}, + {"begin": 0xA0, #Temp 1 + "length": 1}, +] + +def main(): + COMMAND_TYPE_SET_CONFIG = 2 + config_command = struct.pack("@HxxH", + COMMAND_TYPE_SET_CONFIG, + len(reglist)) + for r in reglist: + keep = 1 + if "keep" in r: + keep = r["keep"] + flags = 0 + if "flags" in r: + flags = r["flags"] + monitor_interval = struct.pack("@HHHH", r["begin"], r["length"], keep, flags) + config_command += monitor_interval + + config_packet = struct.pack("H", len(config_command)) + config_command + srvpath = "/var/run/rackmond.sock" + if os.path.exists(srvpath): + client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + client.connect(srvpath) + client.send(config_packet) + +if __name__ == "__main__": + main() diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmond.c b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmond.c new file mode 100644 index 0000000..cce3ba4 --- /dev/null +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmond.c @@ -0,0 +1,637 @@ +#include "modbus.h" +#include "rackmond.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_ACTIVE_ADDRS 12 +#define REGISTER_PSU_STATUS 0x68 + +struct _lock_holder { + pthread_mutex_t *lock; + int held; +}; + +#define lock_holder(holder_name, lock_expr) \ + struct _lock_holder holder_name; \ + holder_name.lock = lock_expr; \ + holder_name.held = 0; + +#define lock_take(holder_name) { \ + pthread_mutex_lock(holder_name.lock); \ + holder_name.held = 1; \ +} + +#define lock_release(holder_name) { \ + if(holder_name.held) { \ + pthread_mutex_unlock(holder_name.lock); \ + holder_name.held = 0; \ + } \ +} + +int scanning = 0; + +typedef struct _rs485_dev { + // hold this for the duration of a command + pthread_mutex_t lock; + int tty_fd; + int gpio_fd; +} rs485_dev; + +typedef struct _register_req { + uint16_t begin; + int num; +} register_req; + +typedef struct register_range_data { + monitor_interval* i; + void* mem_begin; + size_t mem_pos; +} register_range_data; + +typedef struct monitoring_data { + uint8_t addr; + register_range_data range_data[1]; +} monitoring_data; + +typedef struct _rackmond_data { + // global rackmond lock + pthread_mutex_t lock; + // number of register read commands to send to each PSU + int num_reqs; + // register read commands (begin+length) + register_req *reqs; + monitoring_config *config; + + uint8_t num_active_addrs; + uint8_t active_addrs[MAX_ACTIVE_ADDRS]; + monitoring_data* stored_data[MAX_ACTIVE_ADDRS]; + FILE *status_log; + + // timeout in nanosecs + int modbus_timeout; + + int paused; + + rs485_dev rs485; +} rackmond_data; + +rackmond_data world; + +char psu_address(int rack, int shelf, int psu) { + int rack_a = ((rack & 3) << 3); + int shelf_a = ((shelf & 1) << 2); + int psu_a = (psu & 3); + return 0xA0 | rack_a | shelf_a | psu_a; +} + +int modbus_command(rs485_dev* dev, int timeout, char* command, size_t len, char* destbuf, size_t dest_limit, size_t expect) { + int error = 0; + lock_holder(devlock, &dev->lock); + modbus_req req; + req.tty_fd = dev->tty_fd; + req.gpio_fd = dev->gpio_fd; + req.modbus_cmd = command; + req.cmd_len = len; + req.dest_buf = destbuf; + req.dest_limit = dest_limit; + req.timeout = timeout; + req.expected_len = expect != 0 ? expect : dest_limit; + req.scan = scanning; + lock_take(devlock); + int cmd_error = modbuscmd(&req); + CHECK(cmd_error); +cleanup: + lock_release(devlock); + if (error >= 0) { + return req.dest_len; + } + + return error; +} + +int read_registers(rs485_dev *dev, int timeout, uint8_t addr, uint16_t begin, uint16_t num, uint16_t* out) { + int error = 0; + // address, function, begin, length in # of regs + char command[sizeof(addr) + 1 + sizeof(begin) + sizeof(num)]; + // address, function, length (1 byte), data (2 bytes per register), crc + // (VLA) + char response[sizeof(addr) + 1 + 1 + (2 * num) + 2]; + command[0] = addr; + command[1] = MODBUS_READ_HOLDING_REGISTERS; + command[2] = begin << 8; + command[3] = begin & 0xFF; + command[4] = num << 8; + command[5] = num & 0xFF; + + int dest_len = + modbus_command( + dev, timeout, + command, sizeof(addr) + 1 + sizeof(begin) + sizeof(num), + response, sizeof(addr) + 1 + 1 + (2 * num) + 2, 0); + CHECK(dest_len); + + if (dest_len >= 5) { + memcpy(out, response + 3, num * 2); + } else { + log("Unexpected short but CRC correct response!\n"); + error = -1; + goto cleanup; + } + if (response[0] != addr) { + log("Got response for addr %02x when expected %02x\n", response[0], addr); + error = -1; + goto cleanup; + } + if (response[2] != (num * 2)) { + log("Got %d register data bytes when expecting %d\n", response[2], (num * 2)); + error = -1; + goto cleanup; + } +cleanup: + return error; +} + +int sub_uint8s(const void* a, const void* b) { + return (*(uint8_t*)a) - (*(uint8_t*)b); +} + +int check_active_psus() { + int error = 0; + lock_holder(worldlock, &world.lock); + lock_take(worldlock); + if (world.paused == 1) { + usleep(1000); + goto cleanup; + } + if (world.config == NULL) { + lock_release(worldlock); + usleep(5000); + goto cleanup; + } + world.num_active_addrs = 0; + + scanning = 1; + //fprintf(stderr, "Begin presence check: "); + for(int rack = 0; rack < 3; rack++) { + for(int shelf = 0; shelf < 2; shelf++) { + for(int psu = 0; psu < 3; psu++) { + char addr = psu_address(rack, shelf, psu); + uint16_t status = 0; + int err = read_registers(&world.rs485, world.modbus_timeout, addr, REGISTER_PSU_STATUS, 1, &status); + if (err == 0) { + world.active_addrs[world.num_active_addrs] = addr; + world.num_active_addrs++; + //fprintf(stderr, "%02x - active (%04x) ", addr, status); + } else { + dbg("%02x - %d; ", addr, err); + } + } + } + } + //its the only stdlib sort + qsort(world.active_addrs, world.num_active_addrs, + sizeof(uint8_t), sub_uint8s); +cleanup: + scanning = 0; + lock_release(worldlock); + return error; +} + +monitoring_data* alloc_monitoring_data(uint8_t addr) { + size_t size = sizeof(monitoring_data) + + sizeof(register_range_data) * world.config->num_intervals; + for(int i = 0; i < world.config->num_intervals; i++) { + monitor_interval *iv = &world.config->intervals[i]; + int pitch = sizeof(uint32_t) + (sizeof(uint16_t) * iv->len); + int data_size = pitch * iv->keep; + size += data_size; + } + monitoring_data* d = calloc(1, size); + if (d == NULL) { + log("Failed to allocate memory for sensor data.\n"); + return NULL; + } + d->addr = addr; + void* mem = d; + mem = mem + (sizeof(monitoring_data) + + sizeof(register_range_data) * world.config->num_intervals); + for(int i = 0; i < world.config->num_intervals; i++) { + monitor_interval *iv = &world.config->intervals[i]; + int pitch = sizeof(uint32_t) + (sizeof(uint16_t) * iv->len); + int data_size = pitch * iv->keep; + d->range_data[i].i = iv; + d->range_data[i].mem_begin = mem; + d->range_data[i].mem_pos = 0; + mem = mem + data_size; + } + return d; +} + +int sub_storeptrs(const void* va, const void *vb) { + //more *s than i like :/ + monitoring_data* a = *(monitoring_data**)va; + monitoring_data* b = *(monitoring_data**)vb; + //nulls to the end + if (b == NULL && a == NULL) { + return 0; + } + if (b == NULL) { + return -1; + } + if (a == NULL) { + return 1; + } + return a->addr - b->addr; +} + +int alloc_monitoring_datas() { + int error = 0; + if (world.config == NULL) { + goto cleanup; + } + qsort(world.stored_data, MAX_ACTIVE_ADDRS, + sizeof(monitoring_data*), sub_storeptrs); + int data_pos = 0; + for(int i = 0; i < world.num_active_addrs; i++) { + uint8_t addr = world.active_addrs[i]; + while(world.stored_data[data_pos] != NULL && + world.stored_data[data_pos]->addr != addr) { + data_pos++; + } + if (world.stored_data[data_pos] == NULL) { + log("Detected PSU at address 0x%02x\n", addr); + //syslog(LOG_INFO, "Detected PSU at address 0x%02x", addr); + world.stored_data[data_pos] = alloc_monitoring_data(addr); + if (world.stored_data[data_pos] == NULL) { + BAIL("allocation failed\n"); + } + //reset search pos after alloc (post-sorted addrs may already be alloc'd, need to check again) + data_pos = 0; + continue; + } + if (world.stored_data[data_pos]->addr == addr) { + continue; + } + BAIL("shouldn't get here!\n"); + } +cleanup: + return error; +} + +void record_data(register_range_data* rd, uint32_t time, uint16_t* regs) { + int n_regs = (rd->i->len); + int pitch = sizeof(time) + (sizeof(uint16_t) * n_regs); + int mem_size = pitch * rd->i->keep; + + memcpy(rd->mem_begin + rd->mem_pos, &time, sizeof(time)); + rd->mem_pos += sizeof(time); + memcpy(rd->mem_begin + rd->mem_pos, regs, n_regs * sizeof(uint16_t)); + rd->mem_pos += n_regs * sizeof(uint16_t); + rd->mem_pos = rd->mem_pos % mem_size; +} + +int fetch_monitored_data() { + int error = 0; + int data_pos = 0; + lock_holder(worldlock, &world.lock); + lock_take(worldlock); + if (world.paused == 1) { + usleep(1000); + goto cleanup; + } + if (world.config == NULL) { + goto cleanup; + } + lock_release(worldlock); + + usleep(1000); // wait a sec btween PSUs to not overload RT scheduling + // threshold + while(world.stored_data[data_pos] != NULL && data_pos < MAX_ACTIVE_ADDRS) { + uint8_t addr = world.stored_data[data_pos]->addr; + //log("readpsu %02x\n", addr); + for(int r = 0; r < world.config->num_intervals; r++) { + register_range_data* rd = &world.stored_data[data_pos]->range_data[r]; + monitor_interval* i = rd->i; + uint16_t regs[i->len]; + int err = read_registers(&world.rs485, + world.modbus_timeout, addr, i->begin, i->len, regs); + if (err) { + log("Error %d reading %02x registers at %02x from %02x\n", + err, i->len, i->begin, addr); + continue; + } + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + uint32_t timestamp = ts.tv_sec; + if (rd->i->flags & MONITOR_FLAG_ONLY_CHANGES) { + int pitch = sizeof(timestamp) + (sizeof(uint16_t) * i->len); + int lastpos = rd->mem_pos - pitch; + if (lastpos < 0) { + lastpos = (pitch * rd->i->keep) - pitch; + } + if (!memcmp(rd->mem_begin + lastpos + sizeof(timestamp), + regs, sizeof(uint16_t) * i->len) && + memcmp(rd->mem_begin, "\x00\x00\x00\x00", 4)) { + continue; + } + + if (world.status_log) { + time_t rawt; + struct tm* ti; + time(&rawt); + ti = localtime(&rawt); + char timestr[80]; + strftime(timestr, sizeof(timestr), "%b %e %T", ti); + fprintf(world.status_log, + "%s: Change to status register %02x on address %02x. New value: %02x\n", + timestr, i->begin, addr, regs[0]); + fflush(world.status_log); + } + + } + lock_take(worldlock); + record_data(rd, timestamp, regs); + lock_release(worldlock); + } + data_pos++; + } +cleanup: + lock_release(worldlock); + return error; +} + +// check for new psus every N rounds of sensor reads +#define SEARCH_PSUS_EVERY 200 +void* monitoring_loop(void* arg) { + (void) arg; + int until_search = 0; + world.status_log = fopen("/var/log/psu-status.log", "a+"); + while(1) { + if (until_search == 0) { + check_active_psus(); + alloc_monitoring_datas(); + until_search = SEARCH_PSUS_EVERY; + } else { + until_search--; + } + fetch_monitored_data(); + } + return NULL; +} + +int open_rs485_dev(const char* tty_filename, int gpio_num, rs485_dev *dev) { + int error = 0; + int tty_fd, gpio_fd; + char gpio_filename[128]; + dbg("[*] Opening TTY\n"); + tty_fd = open(tty_filename, O_RDWR | O_NOCTTY); + CHECK(tty_fd); + + dbg("[*] Opening GPIO %d\n", gpio_num); + snprintf(gpio_filename, sizeof(gpio_filename), "/sys/class/gpio/gpio%d/value", gpio_num); + gpio_fd = open(gpio_filename, O_WRONLY | O_SYNC); + CHECK(gpio_fd); + + dev->tty_fd = tty_fd; + dev->gpio_fd = gpio_fd; + pthread_mutex_init(&dev->lock, NULL); +cleanup: + return error; +} + +int do_command(int sock, rackmond_command* cmd) { + int error = 0; + lock_holder(worldlock, &world.lock); + switch(cmd->type) { + case COMMAND_TYPE_RAW_MODBUS: + { + uint16_t expected = cmd->raw_modbus.expected_response_length; + int timeout = world.modbus_timeout; + if (cmd->raw_modbus.custom_timeout) { + //ms to us + timeout = cmd->raw_modbus.custom_timeout * 1000; + } + if (expected == 0) { + expected = 1024; + } + char response[expected]; + int response_len = modbus_command( + &world.rs485, timeout, + cmd->raw_modbus.data, cmd->raw_modbus.length, + response, expected, expected); + uint16_t response_len_wire = response_len; + if(response_len < 0) { + uint16_t error = -response_len; + response_len_wire = 0; + send(sock, &response_len_wire, sizeof(uint16_t), 0); + send(sock, &error, sizeof(uint16_t), 0); + break; + } + send(sock, &response_len_wire, sizeof(uint16_t), 0); + send(sock, response, response_len, 0); + break; + } + case COMMAND_TYPE_SET_CONFIG: + { + lock_take(worldlock); + if (world.config != NULL) { + BAIL("rackmond already configured\n"); + } + size_t config_size = sizeof(monitoring_config) + + (sizeof(monitor_interval) * cmd->set_config.config.num_intervals); + world.config = calloc(1, config_size); + memcpy(world.config, &cmd->set_config.config, config_size); + syslog(LOG_INFO, "got configuration"); + lock_release(worldlock); + break; + } + case COMMAND_TYPE_DUMP_DATA_JSON: + { + lock_take(worldlock); + if (world.config == NULL) { + send(sock, "[]", 2, 0); + } else { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + uint32_t now = ts.tv_sec; + send(sock, "[", 1, 0); + int data_pos = 0; + while(world.stored_data[data_pos] != NULL && data_pos < MAX_ACTIVE_ADDRS) { + dprintf(sock, "{\"addr\":%d,\"now\":%d,\"ranges\":[", + world.stored_data[data_pos]->addr, now); + for(int i = 0; i < world.config->num_intervals; i++) { + uint32_t time; + register_range_data *rd = &world.stored_data[data_pos]->range_data[i]; + char* mem_pos = rd->mem_begin; + dprintf(sock,"{\"begin\":%d,\"readings\":[", rd->i->begin); + // want to cut the list off early just before + // the first entry with time == 0 + memcpy(&time, mem_pos, sizeof(time)); + for(int j = 0; j < rd->i->keep && time != 0; j++) { + mem_pos += sizeof(time); + dprintf(sock, "{\"time\":%d,\"data\":\"", time); + for(int c = 0; c < rd->i->len * 2; c++) { + dprintf(sock, "%02x", *mem_pos); + mem_pos++; + } + send(sock, "\"}", 2, 0); + memcpy(&time, mem_pos, sizeof(time)); + if (time == 0) { + break; + } + if ((j+1) < rd->i->keep) { + send(sock, ",", 1, 0); + } + } + send(sock, "]}", 2, 0); + if ((i+1) < world.config->num_intervals) { + send(sock, ",", 1, 0); + } + } + data_pos++; + if (data_pos < MAX_ACTIVE_ADDRS && world.stored_data[data_pos] != NULL) { + send(sock, "]},", 3, 0); + } else { + send(sock, "]}", 2, 0); + } + } + send(sock, "]", 1, 0); + } + lock_release(worldlock); + break; + } + case COMMAND_TYPE_PAUSE_MONITORING: + { + lock_take(worldlock); + uint8_t was_paused = world.paused; + world.paused = 1; + send(sock, &was_paused, sizeof(was_paused), 0); + lock_release(worldlock); + break; + } + case COMMAND_TYPE_START_MONITORING: + { + lock_take(worldlock); + uint8_t was_started = !world.paused; + world.paused = 0; + send(sock, &was_started, sizeof(was_started), 0); + lock_release(worldlock); + break; + } + default: + CHECK(-1); + } +cleanup: + lock_release(worldlock); + return error; +} + +typedef enum { + CONN_WAITING_LENGTH, + CONN_WAITING_BODY +} rackmond_connection_state; + +// receive the command as a length prefixed block +// (uint16_t, followed by data) +// this is all over a local socket, won't be doing +// endian flipping, clients should only be local procs +// compiled for the same arch +int handle_connection(int sock) { + int error = 0; + rackmond_connection_state state = CONN_WAITING_LENGTH; + char bodybuf[1024]; + uint16_t expected_len = 0; + struct pollfd pfd; + int recvret = 0; + pfd.fd = sock; + pfd.events = POLLIN | POLLERR | POLLHUP; + // if you don't do anything for a whole second we bail +next: + CHECKP(poll, poll(&pfd, 1, 1000)); + if (pfd.revents & (POLLERR | POLLHUP)) { + goto cleanup; + } + switch(state) { + case CONN_WAITING_LENGTH: + recvret = recv(sock, &expected_len, 2, MSG_DONTWAIT); + if (recvret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + goto next; + } + if (expected_len == 0 || expected_len > sizeof(bodybuf)) { + // bad length; bail + goto cleanup; + } + state = CONN_WAITING_BODY; + goto next; + break; + case CONN_WAITING_BODY: + recvret = recv(sock, &bodybuf, expected_len, MSG_DONTWAIT); + if (recvret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + goto next; + } + CHECK(do_command(sock, (rackmond_command*) bodybuf)); + } +cleanup: + close(sock); + if (error != 0) { + fprintf(stderr, "Warning: possible error handling user connection (%d)\n", error); + } + return 0; +} + +int main(int argc, char** argv) { + if (getenv("RACKMOND_FOREGROUND") == NULL) { + daemon(0, 0); + } + signal(SIGPIPE, SIG_IGN); + int error = 0; + world.paused = 0; + world.modbus_timeout = 300000; + if (getenv("RACKMOND_TIMEOUT") != NULL) { + world.modbus_timeout = atoll(getenv("RACKMOND_TIMEOUT")); + fprintf(stderr, "Timeout from env: %dms\n", + (world.modbus_timeout / 1000)); + } + world.config = NULL; + pthread_mutex_init(&world.lock, NULL); + verbose = getenv("RACKMOND_VERBOSE") != NULL ? 1 : 0; + openlog("rackmond", 0, LOG_USER); + syslog(LOG_INFO, "rackmon/modbus service starting"); + CHECK(open_rs485_dev(DEFAULT_TTY, DEFAULT_GPIO, &world.rs485)); + pthread_t monitoring_thread; + pthread_create(&monitoring_thread, NULL, monitoring_loop, NULL); + struct sockaddr_un local, client; + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + strcpy(local.sun_path, "/var/run/rackmond.sock"); + local.sun_family = AF_UNIX; + int socknamelen = sizeof(local.sun_family) + strlen(local.sun_path); + unlink(local.sun_path); + CHECKP(bind, bind(sock, (struct sockaddr *)&local, socknamelen)); + CHECKP(listen, listen(sock, 5)); + syslog(LOG_INFO, "rackmon/modbus service listening"); + while(1) { + socklen_t clisocklen = sizeof(struct sockaddr_un); + int clisock = accept(sock, (struct sockaddr*) &client, &clisocklen); + CHECKP(accept, clisock); + CHECK(handle_connection(clisock)); + } + +cleanup: + if (error != 0) { + error = 1; + } + return error; +} diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmond.h b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmond.h new file mode 100644 index 0000000..2c0e8a0 --- /dev/null +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmond.h @@ -0,0 +1,46 @@ +#include + +//would've been nice to have thrift + +// Raw modbus command +// Response is just the raw response data +typedef struct raw_modbus_command { + uint16_t length; + uint16_t expected_response_length; + uint32_t custom_timeout; // 0 for default + char data[1]; +} raw_modbus_command; + +// only store new value if different from most recent +// (for watching changes to status flags registers) +#define MONITOR_FLAG_ONLY_CHANGES 0x1 + +typedef struct monitor_interval { + uint16_t begin; + uint16_t len; + uint16_t keep; // How long of a history to keep? + uint16_t flags; +} monitor_interval; + +typedef struct monitoring_config { + uint16_t num_intervals; + monitor_interval intervals[1]; +} monitoring_config; + +typedef struct set_config_command { + monitoring_config config; +} set_config_command; + +#define COMMAND_TYPE_RAW_MODBUS 0x01 +#define COMMAND_TYPE_SET_CONFIG 0x02 +#define COMMAND_TYPE_DUMP_DATA_JSON 0x03 +#define COMMAND_TYPE_PAUSE_MONITORING 0x04 +#define COMMAND_TYPE_START_MONITORING 0x05 + +typedef struct rackmond_command { + uint16_t type; + union { + raw_modbus_command raw_modbus; + set_config_command set_config; + }; +} rackmond_command; diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmondata.c b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmondata.c new file mode 100644 index 0000000..391b5be --- /dev/null +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/rackmondata.c @@ -0,0 +1,58 @@ +/* + * Copyright 2014-present Facebook. All Rights Reserved. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "modbus.h" +#include "rackmond.h" + +int main(int argc, char **argv) { + int error = 0; + rackmond_command cmd; + int clisock; + uint16_t wire_cmd_len = sizeof(cmd); + struct sockaddr_un rackmond_addr; + cmd.type = COMMAND_TYPE_DUMP_DATA_JSON; + clisock = socket(AF_UNIX, SOCK_STREAM, 0); + CHECKP(socket, clisock); + rackmond_addr.sun_family = AF_UNIX; + strcpy(rackmond_addr.sun_path, "/var/run/rackmond.sock"); + int addr_len = strlen(rackmond_addr.sun_path) + sizeof(rackmond_addr.sun_family); + CHECKP(connect, connect(clisock, (struct sockaddr*) &rackmond_addr, addr_len)); + CHECKP(send, send(clisock, &wire_cmd_len, sizeof(wire_cmd_len), 0)); + CHECKP(send, send(clisock, &cmd, wire_cmd_len, 0)); + char readbuf[256]; + ssize_t n_read; + while((n_read = read(clisock, readbuf, sizeof(readbuf))) > 0) { + write(1, readbuf, n_read); + } +cleanup: + if(error != 0) { + if(errno != 0) { + fprintf(stderr, "%s\n", strerror(errno)); + } + error = 1; + } + return error; +} diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/setup-rackmond.sh b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/setup-rackmond.sh new file mode 100644 index 0000000..85a1e22 --- /dev/null +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon/setup-rackmond.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Copyright 2014-present Facebook. All Rights Reserved. +# +### BEGIN INIT INFO +# Provides: setup-rackmond +# Required-Start: +# Required-Stop: +# Default-Start: S +# Default-Stop: +# Short-Description: Start Rackmon service +### END INIT INFO + +echo -n "Starting rackmon background service..." +/usr/local/bin/rackmond +echo "done." + +echo -n "Configuring rackmon service..." +python /etc/rackmon-config.py +echo "done." diff --git a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon_0.1.bb b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon_0.1.bb index d3e79e4..399f7c0 100644 --- a/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon_0.1.bb +++ b/meta-facebook/meta-wedge/recipes-wedge/rackmon/rackmon_0.1.bb @@ -1,4 +1,19 @@ # Copyright 2014-present Facebook. All Rights Reserved. +# +# This program file 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; version 2 of the License. +# +# 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 in a file named COPYING; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA SUMMARY = "Rackmon Functionality" DESCRIPTION = "Rackmon Functionality" SECTION = "base" @@ -6,7 +21,7 @@ PR = "r1" LICENSE = "GPLv2" LIC_FILES_CHKSUM = "file://modbus.c;beginline=4;endline=16;md5=da35978751a9d71b73679307c4d296ec" -#DEPENDS_append = " update-rc.d-native" +DEPENDS_append = " update-rc.d-native" SRC_URI = "file://Makefile \ file://modbuscmd.c \ @@ -14,6 +29,13 @@ SRC_URI = "file://Makefile \ file://modbus.c \ file://modbus.h \ file://gpiowatch.c \ + file://rackmond.c \ + file://rackmond.h \ + file://rackmondata.c \ + file://setup-rackmond.sh \ + file://rackmon-config.py \ + file://psu-update-delta.py \ + file://hexfile.py \ " S = "${WORKDIR}" @@ -21,6 +43,10 @@ S = "${WORKDIR}" binfiles = "modbuscmd \ modbussim \ gpiowatch \ + rackmond \ + rackmondata \ + psu-update-delta.py \ + hexfile.py \ " #otherfiles = "README" @@ -36,6 +62,11 @@ do_install() { install -m 755 $f ${dst}/$f ln -snf ../fbpackages/${pkgdir}/$f ${bin}/$f done + install -d ${D}${sysconfdir}/init.d + install -d ${D}${sysconfdir}/rcS.d + install -m 755 setup-rackmond.sh ${D}${sysconfdir}/init.d/setup-rackmond.sh + install -m 755 rackmon-config.py ${D}${sysconfdir}/rackmon-config.py + update-rc.d -r ${D} setup-rackmond.sh start 95 2 3 4 5 . } FBPACKAGEDIR = "${prefix}/local/fbpackages" -- cgit v1.1