2010-03-25 23:20:59 +00:00
|
|
|
#include "main.h"
|
|
|
|
#include "bcmxcp.h"
|
|
|
|
#include "bcmxcp_io.h"
|
|
|
|
#include "common.h"
|
|
|
|
#include "usb-common.h"
|
|
|
|
#include "timehead.h"
|
|
|
|
#include "nut_stdint.h" /* for uint16_t */
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <sys/file.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2013-11-24 15:00:12 +00:00
|
|
|
#define SUBDRIVER_NAME "USB communication subdriver"
|
2022-06-29 10:37:36 +00:00
|
|
|
#define SUBDRIVER_VERSION "0.27"
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* communication driver description structure */
|
|
|
|
upsdrv_info_t comm_upsdrv_info = {
|
|
|
|
SUBDRIVER_NAME,
|
|
|
|
SUBDRIVER_VERSION,
|
|
|
|
NULL,
|
|
|
|
0,
|
|
|
|
{ NULL }
|
|
|
|
};
|
|
|
|
|
2012-01-24 10:22:33 +00:00
|
|
|
#define MAX_TRY 4
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
/* Powerware */
|
2013-11-24 15:00:12 +00:00
|
|
|
#define POWERWARE 0x0592
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* Phoenixtec Power Co., Ltd */
|
2013-11-24 15:00:12 +00:00
|
|
|
#define PHOENIXTEC 0x06da
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* Hewlett Packard */
|
2013-11-24 15:00:12 +00:00
|
|
|
#define HP_VENDORID 0x03f0
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
static USBDevice_t curDevice;
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
/* USB functions */
|
|
|
|
usb_dev_handle *nutusb_open(const char *port);
|
|
|
|
int nutusb_close(usb_dev_handle *dev_h, const char *port);
|
|
|
|
/* unified failure reporting: call these often */
|
|
|
|
void nutusb_comm_fail(const char *fmt, ...)
|
|
|
|
__attribute__ ((__format__ (__printf__, 1, 2)));
|
|
|
|
void nutusb_comm_good(void);
|
|
|
|
/* function pointer, set depending on which device is used */
|
2022-06-29 10:37:36 +00:00
|
|
|
/* FIXME? Use usb_ctrl_* typedefs*/
|
|
|
|
static int (*usb_set_descriptor)(usb_dev_handle *udev, unsigned char type,
|
|
|
|
unsigned char index, void *buf, size_t size);
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* usb_set_descriptor() for Powerware devices */
|
2022-06-29 10:37:36 +00:00
|
|
|
/* FIXME? Use usb_ctrl_* typedefs*/
|
|
|
|
static int usb_set_powerware(usb_dev_handle *udev, unsigned char type, unsigned char index, void *buf, size_t size)
|
2010-03-25 23:20:59 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
assert (size < INT_MAX);
|
|
|
|
return usb_control_msg(udev, USB_ENDPOINT_OUT, USB_REQ_SET_DESCRIPTOR, (type << 8) + index, 0, buf, (int)size, 1000);
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
2013-11-24 15:00:12 +00:00
|
|
|
static void *powerware_ups(USBDevice_t *device) {
|
2022-06-29 10:37:36 +00:00
|
|
|
NUT_UNUSED_VARIABLE(device);
|
2010-03-25 23:20:59 +00:00
|
|
|
usb_set_descriptor = &usb_set_powerware;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* usb_set_descriptor() for Phoenixtec devices */
|
2022-06-29 10:37:36 +00:00
|
|
|
/* FIXME? Use usb_ctrl_* typedefs*/
|
|
|
|
static int usb_set_phoenixtec(usb_dev_handle *udev, unsigned char type, unsigned char index, void *buf, size_t size)
|
2010-03-25 23:20:59 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
NUT_UNUSED_VARIABLE(index);
|
|
|
|
NUT_UNUSED_VARIABLE(type);
|
|
|
|
assert (size < INT_MAX);
|
|
|
|
return usb_control_msg(udev, 0x42, 0x0d, (0x00 << 8) + 0x0, 0, buf, (int)size, 1000);
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
2013-11-24 15:00:12 +00:00
|
|
|
static void *phoenixtec_ups(USBDevice_t *device) {
|
2022-06-29 10:37:36 +00:00
|
|
|
NUT_UNUSED_VARIABLE(device);
|
2010-03-25 23:20:59 +00:00
|
|
|
usb_set_descriptor = &usb_set_phoenixtec;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* USB IDs device table */
|
|
|
|
static usb_device_id_t pw_usb_device_table[] = {
|
|
|
|
/* various models */
|
|
|
|
{ USB_DEVICE(POWERWARE, 0x0002), &powerware_ups },
|
|
|
|
|
|
|
|
/* various models */
|
|
|
|
{ USB_DEVICE(PHOENIXTEC, 0x0002), &phoenixtec_ups },
|
|
|
|
|
|
|
|
/* T500 */
|
|
|
|
{ USB_DEVICE(HP_VENDORID, 0x1f01), &phoenixtec_ups },
|
|
|
|
/* T750 */
|
|
|
|
{ USB_DEVICE(HP_VENDORID, 0x1f02), &phoenixtec_ups },
|
2022-06-29 10:37:36 +00:00
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
/* Terminating entry */
|
2022-06-29 10:37:36 +00:00
|
|
|
{ 0, 0, NULL }
|
2010-03-25 23:20:59 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* limit the amount of spew that goes in the syslog when we lose the UPS */
|
2013-11-24 15:00:12 +00:00
|
|
|
#define USB_ERR_LIMIT 10 /* start limiting after 10 in a row */
|
|
|
|
#define USB_ERR_RATE 10 /* then only print every 10th error */
|
2022-06-29 10:37:36 +00:00
|
|
|
#define XCP_USB_TIMEOUT 5000 /* in msec */
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* global variables */
|
2022-06-29 10:37:36 +00:00
|
|
|
static usb_dev_handle *upsdev = NULL;
|
2013-11-24 15:00:12 +00:00
|
|
|
extern int exit_flag;
|
|
|
|
static unsigned int comm_failures = 0;
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* Functions implementations */
|
|
|
|
void send_read_command(unsigned char command)
|
|
|
|
{
|
2012-01-24 10:22:33 +00:00
|
|
|
unsigned char buf[4];
|
|
|
|
|
|
|
|
if (upsdev) {
|
|
|
|
buf[0] = PW_COMMAND_START_BYTE;
|
|
|
|
buf[1] = 0x01; /* data length */
|
|
|
|
buf[2] = command; /* command to send */
|
|
|
|
buf[3] = calc_checksum(buf); /* checksum */
|
2012-06-01 13:55:19 +00:00
|
|
|
upsdebug_hex (3, "send_read_command", buf, 4);
|
2012-01-24 10:22:33 +00:00
|
|
|
usb_set_descriptor(upsdev, USB_DT_STRING, 4, buf, 4); /* FIXME: Ignore error */
|
|
|
|
}
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
void send_write_command(unsigned char *command, size_t command_length)
|
2010-03-25 23:20:59 +00:00
|
|
|
{
|
|
|
|
unsigned char sbuf[128];
|
|
|
|
|
2012-01-24 10:22:33 +00:00
|
|
|
if (upsdev) {
|
|
|
|
/* Prepare the send buffer */
|
|
|
|
sbuf[0] = PW_COMMAND_START_BYTE;
|
|
|
|
sbuf[1] = (unsigned char)(command_length);
|
|
|
|
memcpy(sbuf+2, command, command_length);
|
|
|
|
command_length += 2;
|
|
|
|
|
|
|
|
/* Add checksum */
|
|
|
|
sbuf[command_length] = calc_checksum(sbuf);
|
|
|
|
command_length += 1;
|
2012-06-01 13:55:19 +00:00
|
|
|
upsdebug_hex (3, "send_write_command", sbuf, command_length);
|
2012-01-24 10:22:33 +00:00
|
|
|
usb_set_descriptor(upsdev, USB_DT_STRING, 4, sbuf, command_length); /* FIXME: Ignore error */
|
|
|
|
}
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
2016-07-18 00:11:41 +00:00
|
|
|
#define PW_HEADER_SIZE (PW_HEADER_LENGTH + 1)
|
|
|
|
#define PW_CMD_BUFSIZE 256
|
2010-03-25 23:20:59 +00:00
|
|
|
/* get the answer of a command from the ups. And check that the answer is for this command */
|
2022-06-29 10:37:36 +00:00
|
|
|
ssize_t get_answer(unsigned char *data, unsigned char command)
|
2010-03-25 23:20:59 +00:00
|
|
|
{
|
2016-07-18 00:11:41 +00:00
|
|
|
unsigned char buf[PW_CMD_BUFSIZE], *my_buf = buf;
|
2022-06-29 10:37:36 +00:00
|
|
|
ssize_t res;
|
|
|
|
int endblock, need_data;
|
|
|
|
long elapsed_time; /* milliseconds */
|
|
|
|
ssize_t tail;
|
|
|
|
size_t bytes_read, end_length, length;
|
2010-03-25 23:20:59 +00:00
|
|
|
unsigned char block_number, sequence, seq_num;
|
|
|
|
struct timeval start_time, now;
|
|
|
|
|
2012-01-24 10:22:33 +00:00
|
|
|
if (upsdev == NULL)
|
|
|
|
return -1;
|
|
|
|
|
2016-07-18 00:11:41 +00:00
|
|
|
need_data = PW_HEADER_SIZE; /* 4 - cmd response header length, 1 for csum */
|
2013-11-24 15:00:12 +00:00
|
|
|
end_length = 0; /* total length of sequence(s), not counting header(s) */
|
|
|
|
endblock = 0; /* signal the last sequence in the block */
|
|
|
|
bytes_read = 0; /* total length of data read, including XCP header */
|
2010-03-25 23:20:59 +00:00
|
|
|
res = 0;
|
2022-06-29 10:37:36 +00:00
|
|
|
elapsed_time = 0;
|
2013-11-24 15:00:12 +00:00
|
|
|
seq_num = 1; /* current theoric sequence */
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
upsdebugx(1, "entering get_answer(%x)", command);
|
|
|
|
|
|
|
|
/* Store current time */
|
|
|
|
gettimeofday(&start_time, NULL);
|
2016-07-18 00:11:41 +00:00
|
|
|
memset(&buf, 0x0, PW_CMD_BUFSIZE);
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
|
|
|
|
#pragma GCC diagnostic ignored "-Wunreachable-code"
|
|
|
|
#endif
|
|
|
|
#ifdef __clang__
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
#pragma clang diagnostic ignored "-Wunreachable-code"
|
|
|
|
#endif
|
|
|
|
/* Stay ahead of possible redefinitions... */
|
|
|
|
assert (XCP_USB_TIMEOUT < INT_MAX);
|
|
|
|
#ifdef __clang__
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
#endif
|
|
|
|
|
|
|
|
while ( (!endblock) && ((XCP_USB_TIMEOUT - elapsed_time) > 0) ) {
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* Get (more) data if needed */
|
2016-07-18 00:11:41 +00:00
|
|
|
if (need_data > 0) {
|
2022-06-29 10:37:36 +00:00
|
|
|
res = usb_interrupt_read(upsdev,
|
|
|
|
0x81,
|
|
|
|
(usb_ctrl_charbuf) buf + bytes_read,
|
2016-07-18 00:11:41 +00:00
|
|
|
128,
|
2022-06-29 10:37:36 +00:00
|
|
|
(int)(XCP_USB_TIMEOUT - elapsed_time));
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* Update time */
|
|
|
|
gettimeofday(&now, NULL);
|
2022-06-29 10:37:36 +00:00
|
|
|
elapsed_time = (now.tv_sec - start_time.tv_sec)*1000 +
|
2010-03-25 23:20:59 +00:00
|
|
|
(now.tv_usec - start_time.tv_usec)/1000;
|
|
|
|
|
|
|
|
/* Check libusb return value */
|
|
|
|
if (res < 0)
|
|
|
|
{
|
|
|
|
/* Clear any possible endpoint stalls */
|
|
|
|
usb_clear_halt(upsdev, 0x81);
|
2011-06-01 20:31:49 +00:00
|
|
|
/* continue; */ /* FIXME: seems a break would be better! */
|
2010-03-25 23:20:59 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* this seems to occur on XSlot USB card */
|
|
|
|
if (res == 0)
|
|
|
|
{
|
|
|
|
/* FIXME: */
|
|
|
|
continue;
|
|
|
|
}
|
2016-07-18 00:11:41 +00:00
|
|
|
/* Else, we got some input bytes */
|
2022-06-29 10:37:36 +00:00
|
|
|
bytes_read += (size_t)res;
|
2016-07-18 00:11:41 +00:00
|
|
|
need_data -= res;
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebug_hex(1, "get_answer", buf, bytes_read);
|
|
|
|
}
|
|
|
|
|
2016-07-18 00:11:41 +00:00
|
|
|
if (need_data > 0) /* We need more data */
|
2022-06-29 10:37:36 +00:00
|
|
|
continue;
|
2016-07-18 00:11:41 +00:00
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
/* Now validate XCP frame */
|
|
|
|
/* Check header */
|
2016-07-18 00:11:41 +00:00
|
|
|
if ( my_buf[0] != PW_COMMAND_START_BYTE ) {
|
|
|
|
upsdebugx(2, "get_answer: wrong header 0xab vs %02x", my_buf[0]);
|
|
|
|
/* Sometime we read something wrong. bad cables? bad ports? */
|
|
|
|
my_buf = memchr(my_buf, PW_COMMAND_START_BYTE, bytes_read);
|
|
|
|
if (!my_buf)
|
2022-06-29 10:37:36 +00:00
|
|
|
return -1;
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Read block number byte */
|
|
|
|
block_number = my_buf[1];
|
|
|
|
upsdebugx(1, "get_answer: block_number = %x", block_number);
|
|
|
|
|
|
|
|
/* Check data length byte (remove the header length) */
|
|
|
|
length = my_buf[2];
|
2022-06-29 10:37:36 +00:00
|
|
|
upsdebugx(3, "get_answer: data length = %zu", length);
|
|
|
|
if (bytes_read < (length + PW_HEADER_SIZE)) {
|
2016-07-18 00:11:41 +00:00
|
|
|
if (need_data < 0) --need_data; /* count zerro byte too */
|
|
|
|
need_data += length + 1; /* packet lenght + checksum */
|
|
|
|
upsdebugx(2, "get_answer: need to read %d more data", need_data);
|
2010-03-25 23:20:59 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* Check if Length conforms to XCP (121 for normal, 140 for Test mode) */
|
|
|
|
/* Use the more generous length for testing */
|
2016-07-18 00:11:41 +00:00
|
|
|
if (length > 140) {
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(2, "get_answer: bad length");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if (bytes_read >= SSIZE_MAX) {
|
|
|
|
upsdebugx(2, "get_answer: bad length (incredibly large read)");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
/* Test the Sequence # */
|
|
|
|
sequence = my_buf[3];
|
|
|
|
if ((sequence & PW_SEQ_MASK) != seq_num) {
|
|
|
|
nutusb_comm_fail("get_answer: not the right sequence received %x!!!\n", (sequence & PW_SEQ_MASK));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
upsdebugx(2, "get_answer: sequence number (%x) is ok", (sequence & PW_SEQ_MASK));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Validate checksum */
|
|
|
|
if (!checksum_test(my_buf)) {
|
|
|
|
nutusb_comm_fail("get_answer: checksum error! ");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
upsdebugx(2, "get_answer: checksum is ok");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check if it's the last sequence */
|
|
|
|
if (sequence & PW_LAST_SEQ) {
|
|
|
|
/* we're done receiving data */
|
|
|
|
upsdebugx(2, "get_answer: all data received");
|
|
|
|
endblock = 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
seq_num++;
|
2016-07-18 00:11:41 +00:00
|
|
|
upsdebugx(2, "get_answer: next sequence is %d", seq_num);
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* copy the current valid XCP frame back */
|
2016-07-18 00:11:41 +00:00
|
|
|
memcpy(data+end_length, my_buf + 4, length);
|
2010-03-25 23:20:59 +00:00
|
|
|
/* increment pointers to process the next sequence */
|
|
|
|
end_length += length;
|
2022-06-29 10:37:36 +00:00
|
|
|
|
|
|
|
/* Work around signedness of comparison result, SSIZE_MAX checked above: */
|
|
|
|
tail = (ssize_t)bytes_read;
|
|
|
|
tail -= (ssize_t)(length + PW_HEADER_SIZE);
|
2016-07-18 00:11:41 +00:00
|
|
|
if (tail > 0)
|
2022-06-29 10:37:36 +00:00
|
|
|
my_buf = memmove(&buf[0], my_buf + length + PW_HEADER_SIZE, (size_t)tail);
|
2016-07-18 00:11:41 +00:00
|
|
|
else if (tail == 0)
|
2022-06-29 10:37:36 +00:00
|
|
|
my_buf = &buf[0];
|
|
|
|
else { /* if (tail < 0) */
|
|
|
|
upsdebugx(1, "get_answer(): did not expect to get negative tail size: %zd", tail);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes_read = (size_t)tail;
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
2012-06-01 13:55:19 +00:00
|
|
|
|
|
|
|
upsdebug_hex (5, "get_answer", data, end_length);
|
2022-06-29 10:37:36 +00:00
|
|
|
assert (end_length < SSIZE_MAX);
|
|
|
|
return (ssize_t)end_length;
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Sends a single command (length=1). and get the answer */
|
2022-06-29 10:37:36 +00:00
|
|
|
ssize_t command_read_sequence(unsigned char command, unsigned char *data)
|
2010-03-25 23:20:59 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
ssize_t bytes_read = 0;
|
|
|
|
size_t retry = 0;
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
while ((bytes_read < 1) && (retry < 5)) {
|
|
|
|
send_read_command(command);
|
|
|
|
bytes_read = get_answer(data, command);
|
|
|
|
retry++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bytes_read < 1) {
|
|
|
|
nutusb_comm_fail("Error executing command");
|
|
|
|
dstate_datastale();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bytes_read;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sends a setup command (length > 1) */
|
2022-06-29 10:37:36 +00:00
|
|
|
ssize_t command_write_sequence(unsigned char *command, size_t command_length, unsigned char *answer)
|
2010-03-25 23:20:59 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
ssize_t bytes_read = 0;
|
|
|
|
size_t retry = 0;
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
while ((bytes_read < 1) && (retry < 5)) {
|
|
|
|
send_write_command(command, command_length);
|
2012-06-01 13:55:19 +00:00
|
|
|
sleep(PW_SLEEP);
|
2012-01-24 10:22:33 +00:00
|
|
|
bytes_read = get_answer(answer, command[0]);
|
2010-03-25 23:20:59 +00:00
|
|
|
retry ++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bytes_read < 1) {
|
|
|
|
nutusb_comm_fail("Error executing command");
|
|
|
|
dstate_datastale();
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bytes_read;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
void upsdrv_comm_good(void)
|
2010-03-25 23:20:59 +00:00
|
|
|
{
|
|
|
|
nutusb_comm_good();
|
|
|
|
}
|
|
|
|
|
|
|
|
void upsdrv_initups(void)
|
|
|
|
{
|
|
|
|
upsdev = nutusb_open("USB");
|
|
|
|
}
|
|
|
|
|
|
|
|
void upsdrv_cleanup(void)
|
|
|
|
{
|
|
|
|
upslogx(LOG_ERR, "CLOSING\n");
|
|
|
|
nutusb_close(upsdev, "USB");
|
2022-06-29 10:37:36 +00:00
|
|
|
free(curDevice.Vendor);
|
|
|
|
free(curDevice.Product);
|
|
|
|
free(curDevice.Serial);
|
|
|
|
free(curDevice.Bus);
|
|
|
|
free(curDevice.Device);
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void upsdrv_reconnect(void)
|
|
|
|
{
|
2013-11-24 15:00:12 +00:00
|
|
|
upsdebugx(4, "==================================================");
|
|
|
|
upsdebugx(4, "= device has been disconnected, try to reconnect =");
|
|
|
|
upsdebugx(4, "==================================================");
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
nutusb_close(upsdev, "USB");
|
|
|
|
upsdev = NULL;
|
2012-01-24 10:22:33 +00:00
|
|
|
upsdrv_initups();
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* USB functions */
|
2022-06-29 10:37:36 +00:00
|
|
|
static void nutusb_open_error(const char *port)
|
|
|
|
__attribute__((noreturn));
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
static void nutusb_open_error(const char *port)
|
|
|
|
{
|
|
|
|
printf("Unable to find POWERWARE UPS device on USB bus (%s)\n\n", port);
|
|
|
|
|
|
|
|
printf("Things to try:\n\n");
|
|
|
|
printf(" - Connect UPS device to USB bus\n\n");
|
|
|
|
printf(" - Run this driver as another user (upsdrvctl -u or 'user=...' in ups.conf).\n");
|
|
|
|
printf(" See upsdrvctl(8) and ups.conf(5).\n\n");
|
|
|
|
|
|
|
|
fatalx(EXIT_FAILURE, "Fatal error: unusable configuration");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: this part of the opening can go into common... */
|
2011-01-26 09:35:08 +00:00
|
|
|
static usb_dev_handle *open_powerware_usb(void)
|
2010-03-25 23:20:59 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
#if WITH_LIBUSB_1_0
|
|
|
|
libusb_device **devlist;
|
|
|
|
ssize_t devcount = 0;
|
|
|
|
libusb_device_handle *udev;
|
|
|
|
struct libusb_device_descriptor dev_desc;
|
|
|
|
uint8_t bus;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
devcount = libusb_get_device_list(NULL, &devlist);
|
|
|
|
if (devcount <= 0)
|
|
|
|
fatal_with_errno(EXIT_FAILURE, "No USB device found");
|
|
|
|
|
|
|
|
for (i = 0; i < devcount; i++) {
|
|
|
|
|
|
|
|
libusb_device *device = devlist[i];
|
|
|
|
libusb_get_device_descriptor(device, &dev_desc);
|
|
|
|
|
|
|
|
if (dev_desc.bDeviceClass != LIBUSB_CLASS_PER_INTERFACE) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
curDevice.VendorID = dev_desc.idVendor;
|
|
|
|
curDevice.ProductID = dev_desc.idProduct;
|
|
|
|
bus = libusb_get_bus_number(device);
|
|
|
|
curDevice.Bus = (char *)malloc(4);
|
|
|
|
if (curDevice.Bus == NULL) {
|
|
|
|
libusb_free_device_list(devlist, 1);
|
|
|
|
fatal_with_errno(EXIT_FAILURE, "Out of memory");
|
|
|
|
}
|
|
|
|
sprintf(curDevice.Bus, "%03d", bus);
|
|
|
|
|
|
|
|
/* FIXME: we should also retrieve
|
|
|
|
* dev->descriptor.iManufacturer
|
|
|
|
* dev->descriptor.iProduct
|
|
|
|
* dev->descriptor.iSerialNumber
|
|
|
|
* as in libusb.c->libusb_open()
|
|
|
|
* This is part of the things to put in common... */
|
|
|
|
|
|
|
|
if (is_usb_device_supported(pw_usb_device_table, &curDevice) == SUPPORTED) {
|
|
|
|
libusb_open(device, &udev);
|
|
|
|
libusb_free_device_list(devlist, 1);
|
|
|
|
return udev;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
libusb_free_device_list(devlist, 1);
|
|
|
|
#else /* not WITH_LIBUSB_1_0 */
|
|
|
|
struct usb_bus *busses = usb_get_busses();
|
2010-03-25 23:20:59 +00:00
|
|
|
struct usb_bus *bus;
|
2013-11-24 15:00:12 +00:00
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
for (bus = busses; bus; bus = bus->next)
|
|
|
|
{
|
|
|
|
struct usb_device *dev;
|
2022-06-29 10:37:36 +00:00
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
for (dev = bus->devices; dev; dev = dev->next)
|
|
|
|
{
|
|
|
|
if (dev->descriptor.bDeviceClass != USB_CLASS_PER_INTERFACE) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-11-24 15:00:12 +00:00
|
|
|
curDevice.VendorID = dev->descriptor.idVendor;
|
|
|
|
curDevice.ProductID = dev->descriptor.idProduct;
|
2022-06-29 10:37:36 +00:00
|
|
|
curDevice.Bus = xstrdup(bus->dirname);
|
2013-11-24 15:00:12 +00:00
|
|
|
|
|
|
|
/* FIXME: we should also retrieve
|
|
|
|
* dev->descriptor.iManufacturer
|
|
|
|
* dev->descriptor.iProduct
|
|
|
|
* dev->descriptor.iSerialNumber
|
|
|
|
* as in libusb.c->libusb_open()
|
|
|
|
* This is part of the things to put in common... */
|
|
|
|
|
|
|
|
if (is_usb_device_supported(pw_usb_device_table, &curDevice) == SUPPORTED) {
|
2010-03-25 23:20:59 +00:00
|
|
|
return usb_open(dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-06-29 10:37:36 +00:00
|
|
|
#endif /* WITH_LIBUSB_1_0 */
|
2010-03-25 23:20:59 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
usb_dev_handle *nutusb_open(const char *port)
|
|
|
|
{
|
|
|
|
int dev_claimed = 0;
|
|
|
|
usb_dev_handle *dev_h = NULL;
|
2012-01-24 10:22:33 +00:00
|
|
|
int retry, errout = 0;
|
2022-06-29 10:37:36 +00:00
|
|
|
int ret = 0;
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2012-01-24 10:22:33 +00:00
|
|
|
upsdebugx(1, "entering nutusb_open()");
|
2022-06-29 10:37:36 +00:00
|
|
|
warn_if_bad_usb_port_filename(device_path);
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2012-01-24 10:22:33 +00:00
|
|
|
/* Initialize Libusb */
|
2022-06-29 10:37:36 +00:00
|
|
|
#if WITH_LIBUSB_1_0
|
|
|
|
if (libusb_init(NULL) < 0) {
|
|
|
|
libusb_exit(NULL);
|
|
|
|
fatal_with_errno(EXIT_FAILURE, "Failed to init libusb 1.0");
|
|
|
|
}
|
|
|
|
#else /* not WITH_LIBUSB_1_0 */
|
2012-01-24 10:22:33 +00:00
|
|
|
usb_init();
|
|
|
|
usb_find_busses();
|
|
|
|
usb_find_devices();
|
2022-06-29 10:37:36 +00:00
|
|
|
#endif /* WITH_LIBUSB_1_0 */
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2012-06-01 13:55:19 +00:00
|
|
|
for (retry = 0; retry < MAX_TRY ; retry++)
|
2012-01-24 10:22:33 +00:00
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
dev_h = open_powerware_usb();
|
|
|
|
if (!dev_h) {
|
2012-01-24 10:22:33 +00:00
|
|
|
upsdebugx(1, "Can't open POWERWARE USB device");
|
|
|
|
errout = 1;
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
2012-01-24 10:22:33 +00:00
|
|
|
else {
|
2022-06-29 10:37:36 +00:00
|
|
|
upsdebugx(1, "device %s opened successfully", curDevice.Bus);
|
2012-01-24 10:22:33 +00:00
|
|
|
errout = 0;
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if ((ret = usb_claim_interface(dev_h, 0)) < 0)
|
2012-01-24 10:22:33 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
upsdebugx(1, "Can't claim POWERWARE USB interface: %s", nut_usb_strerror(ret));
|
2012-01-24 10:22:33 +00:00
|
|
|
errout = 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
dev_claimed = 1;
|
|
|
|
errout = 0;
|
|
|
|
}
|
|
|
|
/* FIXME: the above part of the opening can go into common... up to here at least */
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
if ((ret = usb_clear_halt(dev_h, 0x81)) < 0)
|
2012-01-24 10:22:33 +00:00
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
upsdebugx(1, "Can't reset POWERWARE USB endpoint: %s", nut_usb_strerror(ret));
|
2016-07-18 00:11:41 +00:00
|
|
|
if (dev_claimed)
|
2022-06-29 10:37:36 +00:00
|
|
|
usb_release_interface(dev_h, 0);
|
2016-07-18 00:11:41 +00:00
|
|
|
usb_reset(dev_h);
|
|
|
|
sleep(5); /* Wait reconnect */
|
2012-01-24 10:22:33 +00:00
|
|
|
errout = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
errout = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Test if we succeeded */
|
|
|
|
if ( (dev_h != NULL) && dev_claimed && (errout == 0) )
|
|
|
|
break;
|
|
|
|
else {
|
|
|
|
/* Clear errors, and try again */
|
|
|
|
errout = 0;
|
|
|
|
}
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
2012-01-24 10:22:33 +00:00
|
|
|
if (!dev_h && !dev_claimed && retry == MAX_TRY)
|
|
|
|
errout = 1;
|
|
|
|
else
|
|
|
|
return dev_h;
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
if (dev_h && dev_claimed)
|
|
|
|
usb_release_interface(dev_h, 0);
|
2012-01-24 10:22:33 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
nutusb_close(dev_h, port);
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2012-01-24 10:22:33 +00:00
|
|
|
if (errout == 1)
|
|
|
|
nutusb_open_error(port);
|
|
|
|
|
|
|
|
return NULL;
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: this part can go into common... */
|
|
|
|
int nutusb_close(usb_dev_handle *dev_h, const char *port)
|
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
int ret = 0;
|
|
|
|
NUT_UNUSED_VARIABLE(port);
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
if (dev_h)
|
|
|
|
{
|
|
|
|
usb_release_interface(dev_h, 0);
|
2022-06-29 10:37:36 +00:00
|
|
|
#if WITH_LIBUSB_1_0
|
|
|
|
libusb_close(dev_h);
|
|
|
|
libusb_exit(NULL);
|
|
|
|
#else
|
|
|
|
ret = usb_close(dev_h);
|
|
|
|
#endif
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
2022-06-29 10:37:36 +00:00
|
|
|
|
|
|
|
return ret;
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void nutusb_comm_fail(const char *fmt, ...)
|
|
|
|
{
|
2013-11-24 15:00:12 +00:00
|
|
|
int ret;
|
|
|
|
char why[SMALLBUF];
|
|
|
|
va_list ap;
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* this means we're probably here because select was interrupted */
|
|
|
|
if (exit_flag != 0)
|
2013-11-24 15:00:12 +00:00
|
|
|
return; /* ignored, since we're about to exit anyway */
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
comm_failures++;
|
|
|
|
|
|
|
|
if ((comm_failures == USB_ERR_LIMIT) ||
|
|
|
|
((comm_failures % USB_ERR_RATE) == 0))
|
|
|
|
{
|
|
|
|
upslogx(LOG_WARNING, "Warning: excessive comm failures, "
|
|
|
|
"limiting error reporting");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* once it's past the limit, only log once every USB_ERR_LIMIT calls */
|
|
|
|
if ((comm_failures > USB_ERR_LIMIT) &&
|
2012-01-24 10:22:33 +00:00
|
|
|
((comm_failures % USB_ERR_LIMIT) != 0)) {
|
|
|
|
/* Try reconnection */
|
2013-11-24 15:00:12 +00:00
|
|
|
upsdebugx(1, "Got to reconnect!\n");
|
2012-01-24 10:22:33 +00:00
|
|
|
upsdrv_reconnect();
|
2010-03-25 23:20:59 +00:00
|
|
|
return;
|
2012-01-24 10:22:33 +00:00
|
|
|
}
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* generic message if the caller hasn't elaborated */
|
|
|
|
if (!fmt)
|
|
|
|
{
|
2012-01-24 10:22:33 +00:00
|
|
|
upslogx(LOG_WARNING, "Communications with UPS lost - check cabling");
|
2010-03-25 23:20:59 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
ret = vsnprintf(why, sizeof(why), fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
if ((ret < 1) || (ret >= (int) sizeof(why)))
|
|
|
|
upslogx(LOG_WARNING, "usb_comm_fail: vsnprintf needed "
|
|
|
|
"more than %d bytes", (int)sizeof(why));
|
|
|
|
|
|
|
|
upslogx(LOG_WARNING, "Communications with UPS lost: %s", why);
|
|
|
|
}
|
|
|
|
|
|
|
|
void nutusb_comm_good(void)
|
|
|
|
{
|
|
|
|
if (comm_failures == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
upslogx(LOG_NOTICE, "Communications with UPS re-established");
|
|
|
|
comm_failures = 0;
|
|
|
|
}
|