1 /*
2 * richcomm_usb.c - driver for UPS with Richcomm dry-contact to USB
3 * solution, such as 'Sweex Manageable UPS 1000VA'
4 *
5 * May also work on 'Kebo UPS-650D', not tested as of 05/23/2007
6 *
7 * Copyright (C) 2007 Peter van Valderen <p.v.valderen@probu.nl>
8 * Dirk Teurlings <dirk@upexia.nl>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 */
24
25 #include "main.h"
26 #include "usb-common.h"
27
28 /* driver version */
29 #define DRIVER_NAME "Richcomm dry-contact to USB driver"
30 #define DRIVER_VERSION "0.04"
31
32 /* driver description structure */
33 upsdrv_info_t upsdrv_info = {
34 DRIVER_NAME,
35 DRIVER_VERSION,
36 "Peter van Valderen <p.v.valderen@probu.nl>\n"
37 "Dirk Teurlings <dirk@upexia.nl>",
38 DRV_EXPERIMENTAL,
39 { NULL }
40 };
41
42 #define STATUS_REQUESTTYPE 0x21
43 #define REPLY_REQUESTTYPE 0x81
44 #define QUERY_PACKETSIZE 4
45 #define REPLY_PACKETSIZE 6
46 #define REQUEST_VALUE 0x09
47 #define MESSAGE_VALUE 0x200
48 #define INDEX_VALUE 0
49
50 /* limit the amount of spew that goes in the syslog when we lose the UPS (from nut_usb.h) */
51 #define USB_ERR_LIMIT 10 /* start limiting after 10 in a row */
52 #define USB_ERR_RATE 10 /* then only print every 10th error */
53
54 static usb_device_id_t richcomm_usb_id[] = {
55 /* Sweex 1000VA */
56 { USB_DEVICE(0x0925, 0x1234), NULL },
57
58 /* end of list */
59 {-1, -1, NULL}
60 };
61
62 static usb_dev_handle *udev = NULL;
63 static USBDevice_t usbdevice;
64 static unsigned int comm_failures = 0;
65
device_match_func(USBDevice_t * device,void * privdata)66 static int device_match_func(USBDevice_t *device, void *privdata)
67 {
68 switch (is_usb_device_supported(richcomm_usb_id, device))
69 {
70 case SUPPORTED:
71 return 1;
72
73 case POSSIBLY_SUPPORTED:
74 case NOT_SUPPORTED:
75 default:
76 return 0;
77 }
78 }
79
80 static USBDeviceMatcher_t device_matcher = {
81 &device_match_func,
82 NULL,
83 NULL
84 };
85
execute_and_retrieve_query(char * query,char * reply)86 static int execute_and_retrieve_query(char *query, char *reply)
87 {
88 int ret;
89
90 ret = usb_control_msg(udev, STATUS_REQUESTTYPE, REQUEST_VALUE,
91 MESSAGE_VALUE, INDEX_VALUE, query, QUERY_PACKETSIZE, 1000);
92
93 if (ret <= 0) {
94 upsdebugx(3, "send: %s", ret ? usb_strerror() : "timeout");
95 return ret;
96 }
97
98 upsdebug_hex(3, "send", query, ret);
99
100 ret = usb_interrupt_read(udev, REPLY_REQUESTTYPE, reply, REPLY_PACKETSIZE, 1000);
101
102 if (ret <= 0) {
103 upsdebugx(3, "read: %s", ret ? usb_strerror() : "timeout");
104 return ret;
105 }
106
107 upsdebug_hex(3, "read", reply, ret);
108 return ret;
109 }
110
query_ups(char * reply)111 static int query_ups(char *reply)
112 {
113 /*
114 * This packet is a status request to the UPS
115 */
116 char query[QUERY_PACKETSIZE] = { 0x01, 0x00, 0x00, 0x30 };
117
118 return execute_and_retrieve_query(query, reply);
119 }
120
usb_comm_fail(const char * fmt,...)121 static void usb_comm_fail(const char *fmt, ...)
122 {
123 int ret;
124 char why[SMALLBUF];
125 va_list ap;
126
127 /* this means we're probably here because select was interrupted */
128 if (exit_flag != 0) {
129 return; /* ignored, since we're about to exit anyway */
130 }
131
132 comm_failures++;
133
134 if ((comm_failures == USB_ERR_LIMIT) || ((comm_failures % USB_ERR_RATE) == 0)) {
135 upslogx(LOG_WARNING, "Warning: excessive comm failures, limiting error reporting");
136 }
137
138 /* once it's past the limit, only log once every USB_ERR_LIMIT calls */
139 if ((comm_failures > USB_ERR_LIMIT) && ((comm_failures % USB_ERR_LIMIT) != 0)) {
140 return;
141 }
142
143 /* generic message if the caller hasn't elaborated */
144 if (!fmt) {
145 upslogx(LOG_WARNING, "Communications with UPS lost - check cabling");
146 return;
147 }
148
149 va_start(ap, fmt);
150 ret = vsnprintf(why, sizeof(why), fmt, ap);
151 va_end(ap);
152
153 if ((ret < 1) || (ret >= (int) sizeof(why))) {
154 upslogx(LOG_WARNING, "usb_comm_fail: vsnprintf needed more than %d bytes", (int)sizeof(why));
155 }
156
157 upslogx(LOG_WARNING, "Communications with UPS lost: %s", why);
158 }
159
usb_comm_good(void)160 static void usb_comm_good(void)
161 {
162 if (comm_failures == 0) {
163 return;
164 }
165
166 upslogx(LOG_NOTICE, "Communications with UPS re-established");
167 comm_failures = 0;
168 }
169
170 /*
171 * Callback that is called by usb_device_open() that handles USB device
172 * settings prior to accepting the devide. At the very least claim the
173 * device here. Detaching the kernel driver will be handled by the
174 * caller, don't do this here. Return < 0 on error, 0 or higher on
175 * success.
176 */
driver_callback(usb_dev_handle * handle,USBDevice_t * device)177 static int driver_callback(usb_dev_handle *handle, USBDevice_t *device)
178 {
179 if (usb_set_configuration(handle, 1) < 0) {
180 upsdebugx(5, "Can't set USB configuration");
181 return -1;
182 }
183
184 if (usb_claim_interface(handle, 0) < 0) {
185 upsdebugx(5, "Can't claim USB interface");
186 return -1;
187 }
188
189 if (usb_set_altinterface(handle, 0) < 0) {
190 upsdebugx(5, "Can't set USB alternate interface");
191 return -1;
192 }
193
194 if (usb_clear_halt(handle, 0x81) < 0) {
195 upsdebugx(5, "Can't reset USB endpoint");
196 return -1;
197 }
198
199 return 1;
200 }
201
usb_device_close(usb_dev_handle * handle)202 static int usb_device_close(usb_dev_handle *handle)
203 {
204 if (!handle) {
205 return 0;
206 }
207
208 /* usb_release_interface() sometimes blocks and goes
209 into uninterruptible sleep. So don't do it. */
210 /* usb_release_interface(handle, 0); */
211 return usb_close(handle);
212 }
213
usb_device_open(usb_dev_handle ** handlep,USBDevice_t * device,USBDeviceMatcher_t * matcher,int (* callback)(usb_dev_handle * handle,USBDevice_t * device))214 static int usb_device_open(usb_dev_handle **handlep, USBDevice_t *device, USBDeviceMatcher_t *matcher,
215 int (*callback)(usb_dev_handle *handle, USBDevice_t *device))
216 {
217 struct usb_bus *bus;
218
219 /* libusb base init */
220 usb_init();
221 usb_find_busses();
222 usb_find_devices();
223
224 #ifndef __linux__ /* SUN_LIBUSB (confirmed to work on Solaris and FreeBSD) */
225 /* Causes a double free corruption in linux if device is detached! */
226 usb_device_close(*handlep);
227 #endif
228
229 for (bus = usb_busses; bus; bus = bus->next) {
230
231 struct usb_device *dev;
232 usb_dev_handle *handle;
233
234 for (dev = bus->devices; dev; dev = dev->next) {
235
236 int i, ret;
237 USBDeviceMatcher_t *m;
238
239 upsdebugx(4, "Checking USB device [%04x:%04x] (%s/%s)", dev->descriptor.idVendor,
240 dev->descriptor.idProduct, bus->dirname, dev->filename);
241
242 /* supported vendors are now checked by the supplied matcher */
243
244 /* open the device */
245 *handlep = handle = usb_open(dev);
246 if (!handle) {
247 upsdebugx(4, "Failed to open USB device, skipping: %s", usb_strerror());
248 continue;
249 }
250
251 /* collect the identifying information of this
252 device. Note that this is safe, because
253 there's no need to claim an interface for
254 this (and therefore we do not yet need to
255 detach any kernel drivers). */
256
257 free(device->Vendor);
258 free(device->Product);
259 free(device->Serial);
260 free(device->Bus);
261
262 memset(device, 0, sizeof(*device));
263
264 device->VendorID = dev->descriptor.idVendor;
265 device->ProductID = dev->descriptor.idProduct;
266 device->Bus = strdup(bus->dirname);
267
268 if (dev->descriptor.iManufacturer) {
269 char buf[SMALLBUF];
270 ret = usb_get_string_simple(handle, dev->descriptor.iManufacturer,
271 buf, sizeof(buf));
272 if (ret > 0) {
273 device->Vendor = strdup(buf);
274 }
275 }
276
277 if (dev->descriptor.iProduct) {
278 char buf[SMALLBUF];
279 ret = usb_get_string_simple(handle, dev->descriptor.iProduct,
280 buf, sizeof(buf));
281 if (ret > 0) {
282 device->Product = strdup(buf);
283 }
284 }
285
286 if (dev->descriptor.iSerialNumber) {
287 char buf[SMALLBUF];
288 ret = usb_get_string_simple(handle, dev->descriptor.iSerialNumber,
289 buf, sizeof(buf));
290 if (ret > 0) {
291 device->Serial = strdup(buf);
292 }
293 }
294
295 upsdebugx(4, "- VendorID : %04x", device->VendorID);
296 upsdebugx(4, "- ProductID : %04x", device->ProductID);
297 upsdebugx(4, "- Manufacturer : %s", device->Vendor ? device->Vendor : "unknown");
298 upsdebugx(4, "- Product : %s", device->Product ? device->Product : "unknown");
299 upsdebugx(4, "- Serial Number: %s", device->Serial ? device->Serial : "unknown");
300 upsdebugx(4, "- Bus : %s", device->Bus ? device->Bus : "unknown");
301
302 for (m = matcher; m; m = m->next) {
303
304 switch (m->match_function(device, m->privdata))
305 {
306 case 0:
307 upsdebugx(4, "Device does not match - skipping");
308 goto next_device;
309 case -1:
310 fatal_with_errno(EXIT_FAILURE, "matcher");
311 goto next_device;
312 case -2:
313 upsdebugx(4, "matcher: unspecified error");
314 goto next_device;
315 }
316 }
317
318 for (i = 0; i < 3; i++) {
319
320 ret = callback(handle, device);
321 if (ret >= 0) {
322 upsdebugx(4, "USB device [%04x:%04x] opened", device->VendorID, device->ProductID);
323 return ret;
324 }
325 #ifdef HAVE_USB_DETACH_KERNEL_DRIVER_NP
326 /* this method requires at least libusb 0.1.8:
327 * it force device claiming by unbinding
328 * attached driver... From libhid */
329 if (usb_detach_kernel_driver_np(handle, 0) < 0) {
330 upsdebugx(4, "failed to detach kernel driver from USB device: %s", usb_strerror());
331 } else {
332 upsdebugx(4, "detached kernel driver from USB device...");
333 }
334 #endif
335 }
336
337 fatalx(EXIT_FAILURE, "USB device [%04x:%04x] matches, but driver callback failed: %s",
338 device->VendorID, device->ProductID, usb_strerror());
339
340 next_device:
341 usb_close(handle);
342 }
343 }
344
345 *handlep = NULL;
346 upsdebugx(4, "No matching USB device found");
347
348 return -1;
349 }
350
351 /*
352 * Initialise the UPS
353 */
upsdrv_initups(void)354 void upsdrv_initups(void)
355 {
356 char reply[REPLY_PACKETSIZE];
357 int i;
358
359 for (i = 0; usb_device_open(&udev, &usbdevice, &device_matcher, &driver_callback) < 0; i++) {
360
361 if ((i < 32) && (sleep(5) == 0)) {
362 usb_comm_fail("Can't open USB device, retrying ...");
363 continue;
364 }
365
366 fatalx(EXIT_FAILURE,
367 "Unable to find Richcomm dry-contact to USB solution\n\n"
368
369 "Things to try:\n"
370 " - Connect UPS device to USB bus\n"
371 " - Run this driver as another user (upsdrvctl -u or 'user=...' in ups.conf).\n"
372 " See upsdrvctl(8) and ups.conf(5).\n\n"
373
374 "Fatal error: unusable configuration");
375 }
376
377 /*
378 * Read rubbish data a few times; the UPS doesn't seem to respond properly
379 * the first few times after connecting
380 */
381 for (i = 0; i < 5; i++) {
382 query_ups(reply);
383 sleep(1);
384 }
385 }
386
upsdrv_cleanup(void)387 void upsdrv_cleanup(void)
388 {
389 usb_device_close(udev);
390
391 free(usbdevice.Vendor);
392 free(usbdevice.Product);
393 free(usbdevice.Serial);
394 free(usbdevice.Bus);
395 }
396
upsdrv_initinfo(void)397 void upsdrv_initinfo(void)
398 {
399 dstate_setinfo("ups.mfr", "%s", "Richcomm dry-contact to USB solution");
400 dstate_setinfo("ups.model", "%s", usbdevice.Product ? usbdevice.Product : "unknown");
401 dstate_setinfo("ups.serial", "%s", usbdevice.Serial ? usbdevice.Serial : "unknown");
402
403 dstate_setinfo("ups.vendorid", "%04x", usbdevice.VendorID);
404 dstate_setinfo("ups.productid", "%04x", usbdevice.ProductID);
405 }
406
upsdrv_updateinfo(void)407 void upsdrv_updateinfo(void)
408 {
409 char reply[REPLY_PACKETSIZE];
410 int ret, online, battery_normal;
411
412 if (!udev) {
413 ret = usb_device_open(&udev, &usbdevice, &device_matcher, &driver_callback);
414
415 if (ret < 0) {
416 return;
417 }
418 }
419
420 ret = query_ups(reply);
421
422 if (ret < 4) {
423 usb_comm_fail("Query to UPS failed");
424 dstate_datastale();
425
426 usb_device_close(udev);
427 udev = NULL;
428
429 return;
430 }
431
432 usb_comm_good();
433 dstate_dataok();
434
435 /*
436 * 3rd bit of 4th byte indicates whether the UPS is on line (1)
437 * or on battery (0)
438 */
439 online = (reply[3]&4)>>2;
440
441 /*
442 * 2nd bit of 4th byte indicates battery status; normal (1)
443 * or low (0)
444 */
445 battery_normal = (reply[3]&2)>>1;
446
447 status_init();
448
449 if (online) {
450 status_set("OL");
451 } else {
452 status_set("OB");
453 }
454
455 if (!battery_normal) {
456 status_set("LB");
457 }
458
459 status_commit();
460 }
461
462 /*
463 * The shutdown feature is a bit strange on this UPS IMHO, it
464 * switches the polarity of the 'Shutdown UPS' signal, at which
465 * point it will automatically power down once it loses power.
466 *
467 * It will still, however, be possible to poll the UPS and
468 * reverse the polarity _again_, at which point it will
469 * start back up once power comes back.
470 *
471 * Maybe this is the normal way, it just seems a bit strange.
472 *
473 * Please note, this function doesn't power the UPS off if
474 * line power is connected.
475 */
upsdrv_shutdown(void)476 void upsdrv_shutdown(void)
477 {
478 /*
479 * This packet shuts down the UPS, that is,
480 * if it is not currently on line power
481 */
482 char prepare[QUERY_PACKETSIZE] = { 0x02, 0x00, 0x00, 0x00 };
483
484 /*
485 * This should make the UPS turn itself back on once the
486 * power comes back on; which is probably what we want
487 */
488 char restart[QUERY_PACKETSIZE] = { 0x02, 0x01, 0x00, 0x00 };
489 char reply[REPLY_PACKETSIZE];
490
491 execute_and_retrieve_query(prepare, reply);
492
493 /*
494 * have to, the previous command seems to be
495 * ignored if the second command comes right
496 * behind it
497 */
498 sleep(1);
499
500
501 execute_and_retrieve_query(restart, reply);
502 }
503
upsdrv_help(void)504 void upsdrv_help(void)
505 {
506 }
507
upsdrv_makevartable(void)508 void upsdrv_makevartable(void)
509 {
510 }
511