Imported Upstream version 2.4.3
This commit is contained in:
commit
26fb71b504
446 changed files with 148951 additions and 0 deletions
510
drivers/richcomm_usb.c
Normal file
510
drivers/richcomm_usb.c
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
/*
|
||||
* richcomm_usb.c - driver for UPS with Richcomm dry-contact to USB
|
||||
* solution, such as 'Sweex Manageable UPS 1000VA'
|
||||
*
|
||||
* May also work on 'Kebo UPS-650D', not tested as of 05/23/2007
|
||||
*
|
||||
* Copyright (C) 2007 Peter van Valderen <p.v.valderen@probu.nl>
|
||||
* Dirk Teurlings <dirk@upexia.nl>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "main.h"
|
||||
#include "usb-common.h"
|
||||
|
||||
/* driver version */
|
||||
#define DRIVER_NAME "Richcomm dry-contact to USB driver"
|
||||
#define DRIVER_VERSION "0.04"
|
||||
|
||||
/* driver description structure */
|
||||
upsdrv_info_t upsdrv_info = {
|
||||
DRIVER_NAME,
|
||||
DRIVER_VERSION,
|
||||
"Peter van Valderen <p.v.valderen@probu.nl>\n"
|
||||
"Dirk Teurlings <dirk@upexia.nl>",
|
||||
DRV_EXPERIMENTAL,
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define STATUS_REQUESTTYPE 0x21
|
||||
#define REPLY_REQUESTTYPE 0x81
|
||||
#define QUERY_PACKETSIZE 4
|
||||
#define REPLY_PACKETSIZE 6
|
||||
#define REQUEST_VALUE 0x09
|
||||
#define MESSAGE_VALUE 0x200
|
||||
#define INDEX_VALUE 0
|
||||
|
||||
/* limit the amount of spew that goes in the syslog when we lose the UPS (from nut_usb.h) */
|
||||
#define USB_ERR_LIMIT 10 /* start limiting after 10 in a row */
|
||||
#define USB_ERR_RATE 10 /* then only print every 10th error */
|
||||
|
||||
static usb_device_id_t richcomm_usb_id[] = {
|
||||
/* Sweex 1000VA */
|
||||
{ USB_DEVICE(0x0925, 0x1234), NULL },
|
||||
|
||||
/* end of list */
|
||||
{-1, -1, NULL}
|
||||
};
|
||||
|
||||
static usb_dev_handle *udev = NULL;
|
||||
static USBDevice_t usbdevice;
|
||||
static unsigned int comm_failures = 0;
|
||||
|
||||
static int device_match_func(USBDevice_t *device, void *privdata)
|
||||
{
|
||||
switch (is_usb_device_supported(richcomm_usb_id, device->VendorID, device->ProductID))
|
||||
{
|
||||
case SUPPORTED:
|
||||
return 1;
|
||||
|
||||
case POSSIBLY_SUPPORTED:
|
||||
case NOT_SUPPORTED:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static USBDeviceMatcher_t device_matcher = {
|
||||
&device_match_func,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int execute_and_retrieve_query(char *query, char *reply)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = usb_control_msg(udev, STATUS_REQUESTTYPE, REQUEST_VALUE,
|
||||
MESSAGE_VALUE, INDEX_VALUE, query, QUERY_PACKETSIZE, 1000);
|
||||
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "send: %s", ret ? usb_strerror() : "timeout");
|
||||
return ret;
|
||||
}
|
||||
|
||||
upsdebug_hex(3, "send", query, ret);
|
||||
|
||||
ret = usb_interrupt_read(udev, REPLY_REQUESTTYPE, reply, REPLY_PACKETSIZE, 1000);
|
||||
|
||||
if (ret <= 0) {
|
||||
upsdebugx(3, "read: %s", ret ? usb_strerror() : "timeout");
|
||||
return ret;
|
||||
}
|
||||
|
||||
upsdebug_hex(3, "read", reply, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int query_ups(char *reply)
|
||||
{
|
||||
/*
|
||||
* This packet is a status request to the UPS
|
||||
*/
|
||||
char query[QUERY_PACKETSIZE] = { 0x01, 0x00, 0x00, 0x30 };
|
||||
|
||||
return execute_and_retrieve_query(query, reply);
|
||||
}
|
||||
|
||||
static void usb_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);
|
||||
}
|
||||
|
||||
static void usb_comm_good()
|
||||
{
|
||||
if (comm_failures == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
upslogx(LOG_NOTICE, "Communications with UPS re-established");
|
||||
comm_failures = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback that is called by usb_device_open() that handles USB device
|
||||
* settings prior to accepting the devide. At the very least claim the
|
||||
* device here. Detaching the kernel driver will be handled by the
|
||||
* caller, don't do this here. Return < 0 on error, 0 or higher on
|
||||
* success.
|
||||
*/
|
||||
static int driver_callback(usb_dev_handle *handle, USBDevice_t *device)
|
||||
{
|
||||
if (usb_set_configuration(handle, 1) < 0) {
|
||||
upsdebugx(5, "Can't set USB configuration");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (usb_claim_interface(handle, 0) < 0) {
|
||||
upsdebugx(5, "Can't claim USB interface");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (usb_set_altinterface(handle, 0) < 0) {
|
||||
upsdebugx(5, "Can't set USB alternate interface");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (usb_clear_halt(handle, 0x81) < 0) {
|
||||
upsdebugx(5, "Can't reset USB endpoint");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int usb_device_close(usb_dev_handle *handle)
|
||||
{
|
||||
if (!handle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* usb_release_interface() sometimes blocks and goes
|
||||
into uninterruptible sleep. So don't do it. */
|
||||
/* usb_release_interface(handle, 0); */
|
||||
return usb_close(handle);
|
||||
}
|
||||
|
||||
static int usb_device_open(usb_dev_handle **handlep, USBDevice_t *device, USBDeviceMatcher_t *matcher,
|
||||
int (*callback)(usb_dev_handle *handle, USBDevice_t *device))
|
||||
{
|
||||
struct usb_bus *bus;
|
||||
|
||||
/* libusb base init */
|
||||
usb_init();
|
||||
usb_find_busses();
|
||||
usb_find_devices();
|
||||
|
||||
#ifndef __linux__ /* SUN_LIBUSB (confirmed to work on Solaris and FreeBSD) */
|
||||
/* Causes a double free corruption in linux if device is detached! */
|
||||
usb_device_close(*handlep);
|
||||
#endif
|
||||
|
||||
for (bus = usb_busses; bus; bus = bus->next) {
|
||||
|
||||
struct usb_device *dev;
|
||||
usb_dev_handle *handle;
|
||||
|
||||
for (dev = bus->devices; dev; dev = dev->next) {
|
||||
|
||||
int i, ret;
|
||||
USBDeviceMatcher_t *m;
|
||||
|
||||
upsdebugx(4, "Checking USB device [%04x:%04x] (%s/%s)", dev->descriptor.idVendor,
|
||||
dev->descriptor.idProduct, bus->dirname, dev->filename);
|
||||
|
||||
/* supported vendors are now checked by the supplied matcher */
|
||||
|
||||
/* open the device */
|
||||
*handlep = handle = usb_open(dev);
|
||||
if (!handle) {
|
||||
upsdebugx(4, "Failed to open USB device, skipping: %s", usb_strerror());
|
||||
continue;
|
||||
}
|
||||
|
||||
/* collect the identifying information of this
|
||||
device. Note that this is safe, because
|
||||
there's no need to claim an interface for
|
||||
this (and therefore we do not yet need to
|
||||
detach any kernel drivers). */
|
||||
|
||||
free(device->Vendor);
|
||||
free(device->Product);
|
||||
free(device->Serial);
|
||||
free(device->Bus);
|
||||
|
||||
memset(device, 0, sizeof(*device));
|
||||
|
||||
device->VendorID = dev->descriptor.idVendor;
|
||||
device->ProductID = dev->descriptor.idProduct;
|
||||
device->Bus = strdup(bus->dirname);
|
||||
|
||||
if (dev->descriptor.iManufacturer) {
|
||||
char buf[SMALLBUF];
|
||||
ret = usb_get_string_simple(handle, dev->descriptor.iManufacturer,
|
||||
buf, sizeof(buf));
|
||||
if (ret > 0) {
|
||||
device->Vendor = strdup(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (dev->descriptor.iProduct) {
|
||||
char buf[SMALLBUF];
|
||||
ret = usb_get_string_simple(handle, dev->descriptor.iProduct,
|
||||
buf, sizeof(buf));
|
||||
if (ret > 0) {
|
||||
device->Product = strdup(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (dev->descriptor.iSerialNumber) {
|
||||
char buf[SMALLBUF];
|
||||
ret = usb_get_string_simple(handle, dev->descriptor.iSerialNumber,
|
||||
buf, sizeof(buf));
|
||||
if (ret > 0) {
|
||||
device->Serial = strdup(buf);
|
||||
}
|
||||
}
|
||||
|
||||
upsdebugx(4, "- VendorID : %04x", device->VendorID);
|
||||
upsdebugx(4, "- ProductID : %04x", device->ProductID);
|
||||
upsdebugx(4, "- Manufacturer : %s", device->Vendor ? device->Vendor : "unknown");
|
||||
upsdebugx(4, "- Product : %s", device->Product ? device->Product : "unknown");
|
||||
upsdebugx(4, "- Serial Number: %s", device->Serial ? device->Serial : "unknown");
|
||||
upsdebugx(4, "- Bus : %s", device->Bus ? device->Bus : "unknown");
|
||||
|
||||
for (m = matcher; m; m = m->next) {
|
||||
|
||||
switch (m->match_function(device, m->privdata))
|
||||
{
|
||||
case 0:
|
||||
upsdebugx(4, "Device does not match - skipping");
|
||||
goto next_device;
|
||||
case -1:
|
||||
fatal_with_errno(EXIT_FAILURE, "matcher");
|
||||
goto next_device;
|
||||
case -2:
|
||||
upsdebugx(4, "matcher: unspecified error");
|
||||
goto next_device;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
|
||||
ret = callback(handle, device);
|
||||
if (ret >= 0) {
|
||||
upsdebugx(4, "USB device [%04x:%04x] opened", device->VendorID, device->ProductID);
|
||||
return ret;
|
||||
}
|
||||
#ifdef HAVE_USB_DETACH_KERNEL_DRIVER_NP
|
||||
/* this method requires at least libusb 0.1.8:
|
||||
* it force device claiming by unbinding
|
||||
* attached driver... From libhid */
|
||||
if (usb_detach_kernel_driver_np(handle, 0) < 0) {
|
||||
upsdebugx(4, "failed to detach kernel driver from USB device: %s", usb_strerror());
|
||||
} else {
|
||||
upsdebugx(4, "detached kernel driver from USB device...");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
fatalx(EXIT_FAILURE, "USB device [%04x:%04x] matches, but driver callback failed: %s",
|
||||
device->VendorID, device->ProductID, usb_strerror());
|
||||
|
||||
next_device:
|
||||
usb_close(handle);
|
||||
}
|
||||
}
|
||||
|
||||
*handlep = NULL;
|
||||
upsdebugx(4, "No matching USB device found");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialise the UPS
|
||||
*/
|
||||
void upsdrv_initups(void)
|
||||
{
|
||||
char reply[REPLY_PACKETSIZE];
|
||||
int i;
|
||||
|
||||
for (i = 0; usb_device_open(&udev, &usbdevice, &device_matcher, &driver_callback) < 0; i++) {
|
||||
|
||||
if ((i < 32) && (sleep(5) == 0)) {
|
||||
usb_comm_fail("Can't open USB device, retrying ...");
|
||||
continue;
|
||||
}
|
||||
|
||||
fatalx(EXIT_FAILURE,
|
||||
"Unable to find Richcomm dry-contact to USB solution\n\n"
|
||||
|
||||
"Things to try:\n"
|
||||
" - Connect UPS device to USB bus\n"
|
||||
" - Run this driver as another user (upsdrvctl -u or 'user=...' in ups.conf).\n"
|
||||
" See upsdrvctl(8) and ups.conf(5).\n\n"
|
||||
|
||||
"Fatal error: unusable configuration");
|
||||
}
|
||||
|
||||
/*
|
||||
* Read rubbish data a few times; the UPS doesn't seem to respond properly
|
||||
* the first few times after connecting
|
||||
*/
|
||||
for (i = 0; i < 5; i++) {
|
||||
query_ups(reply);
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void upsdrv_cleanup(void)
|
||||
{
|
||||
usb_device_close(udev);
|
||||
|
||||
free(usbdevice.Vendor);
|
||||
free(usbdevice.Product);
|
||||
free(usbdevice.Serial);
|
||||
free(usbdevice.Bus);
|
||||
}
|
||||
|
||||
void upsdrv_initinfo(void)
|
||||
{
|
||||
dstate_setinfo("ups.mfr", "%s", "Richcomm dry-contact to USB solution");
|
||||
dstate_setinfo("ups.model", "%s", usbdevice.Product ? usbdevice.Product : "unknown");
|
||||
dstate_setinfo("ups.serial", "%s", usbdevice.Serial ? usbdevice.Serial : "unknown");
|
||||
|
||||
dstate_setinfo("ups.vendorid", "%04x", usbdevice.VendorID);
|
||||
dstate_setinfo("ups.productid", "%04x", usbdevice.ProductID);
|
||||
}
|
||||
|
||||
void upsdrv_updateinfo(void)
|
||||
{
|
||||
char reply[REPLY_PACKETSIZE];
|
||||
int ret, online, battery_normal;
|
||||
|
||||
if (!udev) {
|
||||
ret = usb_device_open(&udev, &usbdevice, &device_matcher, &driver_callback);
|
||||
|
||||
if (ret < 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ret = query_ups(reply);
|
||||
|
||||
if (ret < 4) {
|
||||
usb_comm_fail("Query to UPS failed");
|
||||
dstate_datastale();
|
||||
|
||||
usb_device_close(udev);
|
||||
udev = NULL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
usb_comm_good();
|
||||
dstate_dataok();
|
||||
|
||||
/*
|
||||
* 3rd bit of 4th byte indicates whether the UPS is on line (1)
|
||||
* or on battery (0)
|
||||
*/
|
||||
online = (reply[3]&4)>>2;
|
||||
|
||||
/*
|
||||
* 2nd bit of 4th byte indicates battery status; normal (1)
|
||||
* or low (0)
|
||||
*/
|
||||
battery_normal = (reply[3]&2)>>1;
|
||||
|
||||
status_init();
|
||||
|
||||
if (online) {
|
||||
status_set("OL");
|
||||
} else {
|
||||
status_set("OB");
|
||||
}
|
||||
|
||||
if (!battery_normal) {
|
||||
status_set("LB");
|
||||
}
|
||||
|
||||
status_commit();
|
||||
}
|
||||
|
||||
/*
|
||||
* The shutdown feature is a bit strange on this UPS IMHO, it
|
||||
* switches the polarity of the 'Shutdown UPS' signal, at which
|
||||
* point it will automatically power down once it loses power.
|
||||
*
|
||||
* It will still, however, be possible to poll the UPS and
|
||||
* reverse the polarity _again_, at which point it will
|
||||
* start back up once power comes back.
|
||||
*
|
||||
* Maybe this is the normal way, it just seems a bit strange.
|
||||
*
|
||||
* Please note, this function doesn't power the UPS off if
|
||||
* line power is connected.
|
||||
*/
|
||||
void upsdrv_shutdown(void)
|
||||
{
|
||||
/*
|
||||
* This packet shuts down the UPS, that is,
|
||||
* if it is not currently on line power
|
||||
*/
|
||||
char prepare[QUERY_PACKETSIZE] = { 0x02, 0x00, 0x00, 0x00 };
|
||||
|
||||
/*
|
||||
* This should make the UPS turn itself back on once the
|
||||
* power comes back on; which is probably what we want
|
||||
*/
|
||||
char restart[QUERY_PACKETSIZE] = { 0x02, 0x01, 0x00, 0x00 };
|
||||
char reply[REPLY_PACKETSIZE];
|
||||
|
||||
execute_and_retrieve_query(prepare, reply);
|
||||
|
||||
/*
|
||||
* have to, the previous command seems to be
|
||||
* ignored if the second command comes right
|
||||
* behind it
|
||||
*/
|
||||
sleep(1);
|
||||
|
||||
|
||||
execute_and_retrieve_query(restart, reply);
|
||||
}
|
||||
|
||||
void upsdrv_help(void)
|
||||
{
|
||||
}
|
||||
|
||||
void upsdrv_makevartable(void)
|
||||
{
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue