~ubuntu-branches/ubuntu/saucy/nut/saucy

1.1.1 by Arnaud Quette
Import upstream version 1.4.2
1
/* hidups.c - prototype HID UPS driver for Network UPS Tools
2
 
3
   Copyright (C) 2001  Russell Kroll <rkroll@exploits.org>
4
 
5
   Based on evtest.c v1.10 - Copyright (c) 1999-2000 Vojtech Pavlik
6
 
7
   This program is free software; you can redistribute it and/or modify
8
   it under the terms of the GNU General Public License as published by
9
   the Free Software Foundation; either version 2 of the License, or 
10
   (at your option) any later version.
11
  
12
   This program is distributed in the hope that it will be useful,
13
   but WITHOUT ANY WARRANTY; without even the implied warranty of
14
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
   GNU General Public License for more details.
16
   
17
   You should have received a copy of the GNU General Public License
18
   along with this program; if not, write to the Free Software
19
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
*/
21
22
#include "hidups.h"
23
#include "main.h"
24
25
	int	fd;
26
27
#ifdef HIDIOCGPHYS
28
	/* standards here are in flux for now */
29
	char *hiddev_prefix = "/dev/usb/hid/hiddev";  /* in devfs */
30
	int  hiddev_limit = 16;             /* no dynamic minors */
31
#endif
32
33
int setvalue(int type, int value);
34
int offdelay = DEFAULT_OFFDELAY;
35
int ondelay = DEFAULT_ONDELAY;
36
37
void upsdrv_shutdown(void)
38
{
39
	/* XXX: replace with a proper shutdown function
40
	fatalx("shutdown not supported"); */
41
	
42
	/* 1) set DelayBeforeStartup */
43
	if (getval ("ondelay"))
44
		ondelay = atoi (getval ("ondelay"));
45
	
46
	setvalue(UPS_WAKEDELAY, ondelay);
47
48
	/* 2) set DelayBeforeShutdown */
49
	if (getval ("offdelay"))
50
		offdelay = atoi (getval ("offdelay"));
51
52
	setvalue(UPS_GRACEDELAY, offdelay);
53
}
54
55
void instcmd(int auxcmd, int dlen, char *data) {}
56
57
void upsdrv_help(void) {}
58
59
void upsdrv_makevartable(void)
60
{
61
    char temp [128];
62
	
63
    /* add command line/conf variables */
64
	sprintf(temp, "Set shutdown delay, in seconds (default=%d).", DEFAULT_OFFDELAY);
65
	addvar (VAR_VALUE, "offdelay", temp);
66
67
	sprintf(temp, "Set startup delay, in ten seconds units for MGE (default=%d).", DEFAULT_ONDELAY);
68
	addvar (VAR_VALUE, "ondelay", temp);
69
	
70
#ifdef HIDIOCGPHYS
71
72
	/* add command line/conf variables */
73
	snprintf(temp, sizeof(temp), "/dev/... prefix (default %s)",
74
		hiddev_prefix);
75
76
	addvar(VAR_VALUE, "HidDevPrefix", temp);
77
78
	snprintf(temp, sizeof(temp), 
79
		"how many devices to check (default %d)       ",
80
		hiddev_limit);
81
82
	addvar(VAR_VALUE, "HidDevLimit", temp);
83
#endif
84
}
85
86
void upsdrv_banner(void)
87
{
88
	printf("Network UPS Tools: HID UPS driver 0.13 (%s)\n\n", UPS_VERSION);
89
90
	experimental_driver = 1;
91
}
92
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
93
static inline int find_application(int fd, int usage) 
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
94
{
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
95
	int	i = 0, ret;
96
97
	while ((ret = ioctl(fd, HIDIOCAPPLICATION, i)) > 0 &&
98
	       ret != usage) 
99
		i++;
100
101
	return (ret == usage);
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
102
}
103
104
static void parse_event(struct hiddev_event *ev)
105
{
106
	int	sc = 0;
107
	static	int	ol = -1, ob = -1, lb = -1;
108
109
	/* XXX: deal with bogosity by throwing it out */
110
	if (ev->value > 3000000) {
111
		upslogx(LOG_INFO, "Ignoring bogus event 0x%x (%d)",
112
			ev->hid, ev->value);
113
		return;
114
	}
115
116
	/* x86 page == ups-specific, ignore these for now */
117
	if ((ev->hid & 0x860000) == 0x860000) {
118
		upslogx(LOG_INFO, "Ignoring x86 page event 0x%x (%d)",
119
			ev->hid, ev->value);
120
		return;
121
	}
122
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
123
	upsdebugx(2, "event(%x, %d)",ev->hid,ev->value);
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
124
	switch (ev->hid) {
125
		case UPS_BATTVOLT:
126
			dstate_setinfo("battery.voltage", "%2.1f", 
127
				ev->value / 100.0);
128
			break;
129
		case BATT_RUNTIME_TO_EMPTY:
130
			dstate_setinfo("battery.runtime", "%d", ev->value);
131
			break;
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
132
		case UPS_LOADPCT:
133
			dstate_setinfo("ups.load", "%d", ev->value);
134
			break;
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
135
		case BATT_REMAINING_CAPACITY:
136
			dstate_setinfo("battery.charge", "%d", ev->value);
137
			break;
138
139
		/* OB/OL/LB: update temp storage and flag changed */
140
		case BATT_DISCHARGING:
141
			if ((ev->value == 0) || (ev->value == 1)) {
142
				ob = ev->value;
143
				sc = 1;
144
			}
145
			else
146
				upslogx(LOG_WARNING,
147
					"Got bogus value for BATT_DISCHARGING: %d",
148
					ev->value);
149
			break;
150
151
		case BATT_AC_PRESENT:
152
			if ((ev->value == 0) || (ev->value == 1)) {
153
				ol = ev->value;
154
				sc = 1;
155
			}
156
			else
157
				upslogx(LOG_WARNING, 
158
					"Got bogus value for BATT_AC_PRESENT: %d",
159
					ev->value);
160
			break;
161
162
		case UPS_SHUTDOWN_IMMINENT:
163
			if ((ev->value == 0) || (ev->value == 1)) {
164
				lb = ev->value;
165
				sc = 1;
166
			}
167
			else
168
				upslogx(LOG_WARNING,
169
					"Got bogus value for UPS_SHUTDOWN_IMMINENT: %d", 
170
					ev->value);
171
			break;
172
173
		/* things that we don't care about */
174
		case BATT_BELOW_RCL:
175
		case BATT_CHARGING:
176
			break;
177
178
		default:
179
			upslogx(LOG_INFO, "Unhandled event: 0x%x (%d)",
180
				ev->hid, ev->value);
181
	}
182
183
	/* deal with any status changes */
184
	if (sc == 0)
185
		return;
186
187
	status_init();
188
189
	if (ol == 1)
190
		status_set("OL");
191
192
	if (ob == 1)
193
		status_set("OB");
194
195
	if (lb == 1)
196
		status_set("LB");
197
198
	status_commit();
199
	dstate_dataok();
200
}
201
202
static int getvalue(int type)
203
{
204
	struct	hiddev_usage_ref uref;
205
206
	/* TODO: build a report table so we don't need HID_REPORT_ID_UNKNOWN */
207
208
	memset(&uref, 0, sizeof(uref));
209
	uref.report_type = HID_REPORT_TYPE_FEATURE;
210
	uref.report_id = HID_REPORT_ID_UNKNOWN;
211
	uref.field_index = 0;
212
	uref.usage_index = 0;
213
	uref.usage_code = type;
214
	uref.value = 0;
215
216
	if (ioctl(fd, HIDIOCGUSAGE, &uref) >= 0)
217
		return uref.value;
218
	else
219
		return -2; /* -1 is valid for Delay values (== not set) */
220
}
221
222
static char *getstring(int type)
223
{
224
	struct	hiddev_usage_ref uref;
225
	struct	hiddev_string_descriptor sdesc;
226
	static	char	str[256];
227
228
	/* TODO: build a report table so we don't need HID_REPORT_ID_UNKNOWN */
229
230
	memset(&uref, 0, sizeof(uref));
231
	memset(&sdesc, 0, sizeof(sdesc));
232
	uref.report_type = HID_REPORT_TYPE_FEATURE;
233
	uref.report_id = HID_REPORT_ID_UNKNOWN;
234
	uref.usage_code = type;
235
236
	snprintf(str, sizeof(str), "Unknown");
237
	if (ioctl(fd, HIDIOCGUSAGE, &uref) == 0) {
238
		if ( 0 != (sdesc.index = uref.value)) {
239
		     if (ioctl(fd, HIDIOCGSTRING, &sdesc) > 0)
240
			  snprintf(str, sizeof(str), "%s", sdesc.value);
241
		     else
242
			  upslog(LOG_ERR, "ioctl HIDIOCGSTRING");
243
		}
244
	}
245
246
	return str;
247
}
248
249
	/* results of querying some x86 page values on my APC UPS */
250
251
	/* note to APC: i can decode your "secret protocol" just by        *
252
	 * looking at it.  you're not helping anyone by keeping it closed! */
253
254
	/* 0x860060 == "441HMLL" - looks like a 'capability' string	*/
255
	/*	    == locale 4, 4 choices, 1 byte each			*/
256
	/*          == line sensitivity (high, medium, low, low)	*/
257
258
	/* 0x860013 == 44200155090 - capability again			*/
259
	/*          == locale 4, 4 choices, 2 bytes, 00, 15, 50, 90	*/
260
	/*          == minimum charge to return online			*/
261
262
	/* 0x860062 == D43133136127130					*/
263
	/*          == locale D, 4 choices, 3 bytes, 133, 136, 127, 130	*/
264
	/*          == high transfer voltage				*/
265
266
	/* 0x860064 == D43103100097106					*/
267
	/*          == locale D, 4 choices, 3 bytes, 103, 100, 097, 106	*/
268
	/*          == low transfer voltage				*/
269
270
	/* 0x860066 == 441HMLL (see 860060)				*/	
271
272
	/* 0x860074 == 4410TLN						*/
273
	/*          == locale 4, 4 choices, 1 byte, 0, T, L, N		*/
274
	/*          == alarm setting (5s, 30s, low battery, none)	*/
275
276
	/* 0x860077 == 443060180300600					*/
277
	/*          == locale 4, 4 choices, 3 bytes, 060,180,300,600	*/
278
	/*          == wake-up delay (after power returns)		*/
279
280
void upsdrv_updateinfo(void) 
281
{
282
	fd_set	fdset;
283
	struct	timeval	tv;
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
284
	int	rd;
285
	unsigned int	i;
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
286
	struct	hiddev_event ev[64];
287
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
288
	/* For some reason, APC XS BX1500 does not report changes
289
	 * in LOADPCT without HIDIOCINITREPORT. */
290
	ioctl(fd, HIDIOCINITREPORT, 0);
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
291
	FD_ZERO(&fdset);
292
	FD_SET(fd, &fdset);
293
	tv.tv_sec = 0;
294
	tv.tv_usec = 0;
295
	rd = select(fd+1, &fdset, NULL, NULL, &tv);
296
297
	/* XXX: alarm around this read */
298
	if (rd > 0) {
299
		rd = read(fd, ev, sizeof(ev));
300
301
		if (rd < (int) sizeof(ev[0])) {
302
			if (rd < 0)
303
				fatal("read");
304
			else {
305
				upslog(LOG_INFO, "short read from device");
306
				dstate_datastale();
307
			}
308
		}
309
	    
310
		for (i = 0; i < rd / sizeof(ev[0]); i++)
311
			parse_event(&ev[i]);
312
	}	/* if rd > 0 */
313
}
314
315
static void addhidvalue(int query, const char *varname)
316
{
317
	int	val;
318
319
	val = getvalue(query);
320
321
	if (val < -1)
322
		return;
323
324
	/* XXX: deal with "3 million" kernel bogosity for now */
325
	if (val > 3000000) {
326
		int	i;
327
328
		for (i = 0; i < 5; i++) {
329
			val = getvalue(query);
330
			if (val < 3000000)
331
				break;
332
		}
333
334
		if (val > 3000000) {
335
			upslogx(LOG_WARNING, "Unable to add 0x%x: got bogus value %d",
336
				query, val);
337
			return;
338
		}
339
	}
340
341
	dstate_setinfo(varname, "%d", val);
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
342
	upsdebugx(2, "addhidvalue(%x, %s): obtained %d", 
343
	  query, varname, val);
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
344
}
345
346
int setvalue(int type, int value)
347
{
348
	struct	hiddev_usage_ref uref;
349
	struct hiddev_report_info rinfo;
350
351
	/* 1) issue a get to retrieve all fields values */
352
	memset(&uref, 0, sizeof(uref));
353
	uref.report_type = HID_REPORT_TYPE_FEATURE;
354
	uref.report_id = HID_REPORT_ID_UNKNOWN;
355
	uref.field_index = 0;
356
	uref.usage_index = 0;
357
	uref.usage_code = type;
358
	uref.value = 0;
359
360
	if (ioctl(fd, HIDIOCGUSAGE, &uref) >= 0) {
361
		
362
		upsdebugx(2, "setvalue(%x, %d): obtained %d (report %x))", 
363
			type, value, uref.value, uref.report_id);
364
		
365
		/* get report info */
366
		rinfo.report_type = HID_REPORT_TYPE_FEATURE;
367
		rinfo.report_id = uref.report_id;
368
		
369
		if (ioctl(fd, HIDIOCGREPORT, &rinfo) >= 0) {
370
		
371
			/* set usage in report */
372
			uref.report_type = HID_REPORT_TYPE_OUTPUT;
373
			uref.value = value;
374
			if (ioctl(fd, HIDIOCSUSAGE, &uref) >= 0) {
375
	
376
				/* write report to device */
377
				rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
378
				rinfo.report_id = uref.report_id;
379
				if (ioctl(fd, HIDIOCSREPORT, &rinfo) >= 0) {
380
				
381
					upslogx(1, "setvalue(): report sent successfully");
382
					return 1;
383
				}
384
			} /* for MGE (maybe others !), use HID_REPORT_TYPE_FEATURE */
385
			else {
386
				uref.report_type = HID_REPORT_TYPE_FEATURE;
387
				if (ioctl(fd, HIDIOCSUSAGE, &uref) >= 0) {
388
	
389
					/* write report to device */
390
					rinfo.report_type = HID_REPORT_TYPE_FEATURE;
391
					if (ioctl(fd, HIDIOCSREPORT, &rinfo) >= 0) {
392
					
393
						upslogx(1, "setvalue(using type = feature): report sent successfully");
394
						return 1;
395
					}
396
				}
397
			}
398
		}
399
		return -1;
400
	}
401
	else
402
		return -1;
403
}
404
405
/* handler for settable variables in UPS*/
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
406
int setvar(const char *varname, const char *val)
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
407
{
408
	/* int setvalue(int type, int value) */
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
409
410
	return STAT_SET_UNKNOWN;
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
411
}
412
413
void upsdrv_initinfo(void) 
414
{
415
	int	val;
416
	char	*str, *ptr;
417
418
	dstate_setinfo("ups.mfr", "Generic");
419
	
420
	str = getstring(UPS_IMFR);
421
422
	if (str)
423
		dstate_setinfo("ups.mfr", "%s", str);
424
425
	str = getstring(UPS_IPRODUCT);
426
427
	if (str) {
428
429
		/* try to trim this back to something reasonable */
430
		if ((!strncmp(str, "BackUPS Pro", 11)) ||
431
			(!strncmp(str, "Back-UPS ES", 11)) ||
432
			(!strncmp(str, "Smart-UPS", 9))) {
433
			ptr = strstr(str, "FW");
434
			if (ptr)
435
				*(ptr - 1) = '\0';
436
437
			/* we can be pretty sure of this now */
438
			dstate_setinfo("ups.mfr", "%s", "APC");
439
		}
440
441
		/* non-Pro models seem to have USB ports too now */
442
		if ((!strncmp(str, "BackUPS ", 8)) && (isdigit(str[8]))) {
443
			ptr = strstr(&str[8], " ");
444
			if (ptr)
445
				*(ptr - 1) = '\0';
446
447
			/* we can be pretty sure of this now */
448
			dstate_setinfo("ups.mfr", "%s", "APC");
449
		}
450
451
		dstate_setinfo("ups.model", "%s", str);
452
	}
453
	else
454
		dstate_setinfo("ups.model", "Generic USB UPS");
455
456
	str = getstring(UPS_ISERIAL);
457
458
	if (str)
459
		dstate_setinfo("ups.serial", "%s", str);
460
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
461
	str = getstring(BATT_ICHEMISTRY);
462
	if (str)
463
		dstate_setinfo("battery.chemistry", "%s", str);
464
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
465
	/* seed the status register */
466
467
	val = getvalue(BATT_AC_PRESENT);
468
469
	if (val == 1)
470
		dstate_setinfo("ups.status", "OL");
471
	else
472
		dstate_setinfo("ups.status", "OB");
473
474
	val = getvalue(UPS_BATTVOLT);
475
476
	if (val > 0)
477
		dstate_setinfo("battery.voltage", "%2.1f", val / 100.0);
478
479
	/* XXX: set capacitymode to percent */
480
	addhidvalue(BATT_REMAINING_CAPACITY, "battery.charge");
481
	addhidvalue(BATT_RUNTIME_TO_EMPTY, "battery.runtime");
482
	addhidvalue(UPS_LOADPCT, "ups.load");	
483
484
	addhidvalue(UPS_WAKEDELAY, "ups.delay.start");
485
	addhidvalue(UPS_GRACEDELAY, "ups.delay.shutdown");
486
487
	/* install handlers */
488
	upsh.setvar = setvar;
489
490
	dstate_dataok();
491
}
492
493
static void open_usb(const char *port, int flags)
494
{
495
#ifdef HIDIOCGPHYS
496
	int  i;
497
#endif
498
499
    /* device paths, like /dev/usb/hiddevN, are unstable in any
500
     * hotplug-oriented bus (like usb) except in exotic cases
501
     * where device enumeration order is predictable.
502
     */
503
    if (strncmp (port, "usb-", 4) != 0) {
504
         if ((fd = open (port, flags)) < 0)
505
              fatal("hiddev path open %s", port);
506
         return;
507
    }
508
509
#ifdef HIDIOCGPHYS
510
    /* better: stable ids, which don't change unless usb (or pci)
511
     * topology morphs.  like usb-00:02.3-3.4:0, where
512
     *  - '00:02.3' is a usb host controller id, in this case a
513
     *     pci slot name which is stable enough for most purposes.
514
     *  - '3.4' walks the usb tree:  start at the third root hub
515
     *     port, which has another hub.  then go to the ups on the
516
     *     fourth port of that hub.
517
     *  - ':0' says interface zero on that ups
518
     *
519
     * to use those we need to scan all the hid device paths, but
520
     * when we're done we know that a usb mouse or keyboard config
521
     * change won't have broken our ups config.
522
     */
523
    if (getval ("HidDevPrefix"))
524
         hiddev_prefix = getval ("HidDevPrefix");
525
    if (getval ("HidDevLimit"))
526
         hiddev_limit = atoi (getval ("HidDevLimit"));
527
    for (i = 0; i < hiddev_limit; i++) {
528
         char tmp [128];
529
530
         snprintf (tmp, sizeof tmp, "%s%d", hiddev_prefix, i);
531
         if ((fd = open (tmp, flags)) < 0)
532
              continue;
533
534
         if (ioctl (fd, HIDIOCGPHYS(sizeof tmp), &tmp) > 0) {
535
              if (strcmp (port, tmp) == 0)
536
                   return;
537
         }
538
         close (fd);
539
    }
540
    fatalx("can't match %s, tried up to  %s%d",
541
              port, hiddev_prefix, hiddev_limit);
542
#else
543
#warning "kernel doesn't return HIDDEV physical port paths"
544
    fatalx("can't understand %s, no kernel support", port);
545
#endif /* HIDIOCGPHYS */
546
}
547
548
void upsdrv_initups(void)
549
{
550
	char	name[256];
551
552
	open_usb(device_path, O_RDONLY);
553
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
554
	if ((!find_application(fd, UPS_USAGE)) &&
555
		(!find_application(fd, POWER_USAGE)))
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
556
		fatalx("%s is not a UPS\n", device_path);
557
558
	ioctl(fd, HIDIOCGNAME(sizeof(name)), name);
559
	printf("Detected %s\n", name);
560
	printf("on port %s\n", device_path);
561
1.1.2 by Reinhard Tartler
Import upstream version 2.0.2
562
	ioctl(fd, HIDIOCINITREPORT, 0);
1.1.1 by Arnaud Quette
Import upstream version 1.4.2
563
}
564
565
void upsdrv_cleanup(void)
566
{
567
}