nut/drivers/bcmxcp_usb.c

496 lines
12 KiB
C
Raw Normal View History

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>
#include <usb.h>
#define SUBDRIVER_NAME "USB communication subdriver"
#define SUBDRIVER_VERSION "0.18"
/* communication driver description structure */
upsdrv_info_t comm_upsdrv_info = {
SUBDRIVER_NAME,
SUBDRIVER_VERSION,
NULL,
0,
{ NULL }
};
/* Powerware */
#define POWERWARE 0x0592
/* Phoenixtec Power Co., Ltd */
#define PHOENIXTEC 0x06da
/* Hewlett Packard */
#define HP_VENDORID 0x03f0
/* 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 */
int (*usb_set_descriptor)(usb_dev_handle *udev, unsigned char type,
unsigned char index, void *buf, int size);
/* usb_set_descriptor() for Powerware devices */
static int usb_set_powerware(usb_dev_handle *udev, unsigned char type, unsigned char index, void *buf, int size)
{
return usb_control_msg(udev, USB_ENDPOINT_OUT, USB_REQ_SET_DESCRIPTOR, (type << 8) + index, 0, buf, size, 1000);
}
static void *powerware_ups(void) {
usb_set_descriptor = &usb_set_powerware;
return NULL;
}
/* usb_set_descriptor() for Phoenixtec devices */
static int usb_set_phoenixtec(usb_dev_handle *udev, unsigned char type, unsigned char index, void *buf, int size)
{
return usb_control_msg(udev, 0x42, 0x0d, (0x00 << 8) + 0x0, 0, buf, size, 1000);
}
static void *phoenixtec_ups(void) {
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 },
/* Terminating entry */
{ -1, -1, NULL }
};
/* limit the amount of spew that goes in the syslog when we lose the UPS */
#define USB_ERR_LIMIT 10 /* start limiting after 10 in a row */
#define USB_ERR_RATE 10 /* then only print every 10th error */
#define XCP_USB_TIMEOUT 5000
/* global variables */
usb_dev_handle *upsdev = NULL;
extern int exit_flag;
static unsigned int comm_failures = 0;
/* Functions implementations */
void send_read_command(unsigned char command)
{
unsigned char buf[4];
buf[0] = PW_COMMAND_START_BYTE;
buf[1] = 0x01; /* data length */
buf[2] = command; /* command to send */
buf[3] = calc_checksum(buf); /* checksum */
usb_set_descriptor(upsdev, USB_DT_STRING, 4, buf, 4); /* FIXME: Ignore error */
}
void send_write_command(unsigned char *command, int command_length)
{
unsigned char sbuf[128];
/* 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;
usb_set_descriptor(upsdev, USB_DT_STRING, 4, sbuf, command_length); /* FIXME: Ignore error */
}
/* get the answer of a command from the ups. And check that the answer is for this command */
int get_answer(unsigned char *data, unsigned char command)
{
unsigned char buf[1024], *my_buf = buf;
int length, end_length, res, endblock, bytes_read, ellapsed_time;
unsigned char block_number, sequence, seq_num;
struct timeval start_time, now;
length = 1; /* non zero to enter the read loop */
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 */
res = 0;
ellapsed_time = 0;
seq_num = 1; /* current theoric sequence */
upsdebugx(1, "entering get_answer(%x)", command);
/* Store current time */
gettimeofday(&start_time, NULL);
while ( (!endblock) && ((XCP_USB_TIMEOUT - ellapsed_time) > 0) ) {
/* Get (more) data if needed */
if ((length - bytes_read) > 0) {
res = usb_interrupt_read(upsdev, 0x81,
(char *)&buf[bytes_read],
(PW_ANSWER_MAX_SIZE - bytes_read),
(XCP_USB_TIMEOUT - ellapsed_time));
/* Update time */
gettimeofday(&now, NULL);
ellapsed_time = (now.tv_sec - start_time.tv_sec)*1000 +
(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;
}
/* Else, we got some input bytes */
bytes_read += res;
upsdebug_hex(1, "get_answer", buf, bytes_read);
}
/* Now validate XCP frame */
/* Check header */
if ( my_buf[0] != 0xAB ) {
upsdebugx(2, "get_answer: wrong header");
return -1;
}
/* These validations seem not needed! */
/* Read block number byte */
block_number = my_buf[1];
upsdebugx(1, "get_answer: block_number = %x", block_number);
#if 0
if (command <= 0x43) {
if ((command - 0x30) != block_number){
nutusb_comm_fail("Receive error (Request command): BLOCK: %x (instead of %x), COMMAND: %x!\n",
block_number, (command - 0x30), command);
return -1;
}
}
if (command >= 0x89) {
if ((command == 0xA0) && (block_number != 0x01)){
nutusb_comm_fail("Receive error (Request command): BLOCK: %x (instead of 0x01), COMMAND: %x!\n", block_number, command);
return -1;
}
else if ((command != 0xA0) && (block_number != 0x09)){
nutusb_comm_fail("Receive error (Request command): BLOCK: %x (instead of 0x09), COMMAND: %x!\n", block_number, command);
return -1;
}
}
#endif /* if 0 */
/* Check data length byte (remove the header length) */
length = my_buf[2];
upsdebugx(3, "get_answer: data length = %d", length);
if ((bytes_read - 5) < length) {
upsdebugx(2, "get_answer: need to read %d more data", length - (bytes_read - 5));
continue;
}
/* Check if Length conforms to XCP (121 for normal, 140 for Test mode) */
/* Use the more generous length for testing */
if (length > 140 ) {
upsdebugx(2, "get_answer: bad length");
return -1;
}
/* 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++;
}
/* copy the current valid XCP frame back */
memcpy(data+end_length, my_buf+4, length);
/* increment pointers to process the next sequence */
end_length += length;
my_buf += length + 5;
}
upsdebugx(4, "get_answer: exiting (len=%d)", end_length);
return end_length;
}
/* Sends a single command (length=1). and get the answer */
int command_read_sequence(unsigned char command, unsigned char *data)
{
int bytes_read = 0;
int retry = 0;
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) */
int command_write_sequence(unsigned char *command, int command_length, unsigned char *answer)
{
int bytes_read = 0;
int retry = 0;
while ((bytes_read < 1) && (retry < 5)) {
send_write_command(command, command_length);
bytes_read = get_answer(answer, command[0]);
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");
}
void upsdrv_reconnect(void)
{
upslogx(LOG_WARNING, "RECONNECT USB DEVICE\n");
nutusb_close(upsdev, "USB");
upsdev = NULL;
sleep(3);
upsdrv_initups();
}
/* USB functions */
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
{
struct usb_bus *busses = usb_get_busses();
struct usb_bus *bus;
for (bus = busses; bus; bus = bus->next)
{
struct usb_device *dev;
for (dev = bus->devices; dev; dev = dev->next)
{
if (dev->descriptor.bDeviceClass != USB_CLASS_PER_INTERFACE) {
continue;
}
if (is_usb_device_supported(pw_usb_device_table,
dev->descriptor.idVendor, dev->descriptor.idProduct) == SUPPORTED) {
return usb_open(dev);
}
}
}
return 0;
}
usb_dev_handle *nutusb_open(const char *port)
{
static int libusb_init = 0;
int dev_claimed = 0;
usb_dev_handle *dev_h = NULL;
int retry;
if (!libusb_init)
{
/* Initialize Libusb */
usb_init();
libusb_init = 1;
usb_find_busses();
usb_find_devices();
}
for (retry = 0; dev_h == NULL && retry < 32; retry++)
{
struct timespec t = {5, 0};
dev_h = open_powerware_usb();
if (!dev_h) {
upslogx(LOG_WARNING, "Can't open POWERWARE USB device, retrying ...");
if (nanosleep(&t, NULL) < 0 && errno == EINTR)
break;
}
}
if (!dev_h)
{
upslogx(LOG_ERR, "Can't open POWERWARE USB device");
goto errout;
}
else
upsdebugx(1, "device %s opened successfully", usb_device(dev_h)->filename);
if (usb_claim_interface(dev_h, 0) < 0)
{
upslogx(LOG_ERR, "Can't claim POWERWARE USB interface: %s", usb_strerror());
goto errout;
}
else
dev_claimed = 1;
/* FIXME: this part of the opening can go into common... up to here at least */
if (usb_clear_halt(dev_h, 0x81) < 0)
{
upslogx(LOG_ERR, "Can't reset POWERWARE USB endpoint: %s", usb_strerror());
goto errout;
}
return dev_h;
errout:
if (dev_h && dev_claimed)
usb_release_interface(dev_h, 0);
if (dev_h)
usb_close(dev_h);
nutusb_open_error(port);
return 0;
}
/* FIXME: this part can go into common... */
int nutusb_close(usb_dev_handle *dev_h, const char *port)
{
if (dev_h)
{
usb_release_interface(dev_h, 0);
return usb_close(dev_h);
}
return 0;
}
void nutusb_comm_fail(const char *fmt, ...)
{
int ret;
char why[SMALLBUF];
va_list ap;
/* this means we're probably here because select was interrupted */
if (exit_flag != 0)
return; /* ignored, since we're about to exit anyway */
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) &&
((comm_failures % USB_ERR_LIMIT) != 0))
return;
/* generic message if the caller hasn't elaborated */
if (!fmt)
{
upslogx(LOG_WARNING, "Communications with UPS lost"
" - check cabling");
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;
}