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