xref: /openbsd-src/sys/dev/usb/upd.c (revision 6acf6676b252a23c64b85c3f0f483cf483277ff1)
1 /*	$OpenBSD: upd.c,v 1.15 2015/04/27 07:37:19 mpi Exp $ */
2 
3 /*
4  * Copyright (c) 2014 Andre de Oliveira <andre@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 /* Driver for USB Power Devices sensors */
20 
21 #include <sys/param.h>
22 #include <sys/systm.h>
23 #include <sys/kernel.h>
24 #include <sys/malloc.h>
25 #include <sys/device.h>
26 #include <sys/sensors.h>
27 
28 #include <dev/usb/hid.h>
29 #include <dev/usb/usb.h>
30 #include <dev/usb/usbdi.h>
31 #include <dev/usb/usbdevs.h>
32 #include <dev/usb/usbhid.h>
33 #include <dev/usb/uhidev.h>
34 #include <dev/usb/usbdi_util.h>
35 
36 #ifdef UPD_DEBUG
37 #define DPRINTF(x)	do { printf x; } while (0)
38 #else
39 #define DPRINTF(x)
40 #endif
41 
42 #define DEVNAME(sc)	((sc)->sc_hdev.sc_dev.dv_xname)
43 
44 struct upd_usage_entry {
45 	uint8_t			usage_pg;
46 	uint8_t			usage_id;
47 	enum sensor_type	senstype;
48 	char			*usage_name; /* sensor string */
49 };
50 
51 static struct upd_usage_entry upd_usage_table[] = {
52 	{ HUP_BATTERY,	HUB_REL_STATEOF_CHARGE,
53 	    SENSOR_PERCENT,	 "RelativeStateOfCharge" },
54 	{ HUP_BATTERY,	HUB_ABS_STATEOF_CHARGE,
55 	    SENSOR_PERCENT,	 "AbsoluteStateOfCharge" },
56 	{ HUP_BATTERY,	HUB_REM_CAPACITY,
57 	    SENSOR_PERCENT,	 "RemainingCapacity" },
58 	{ HUP_BATTERY,	HUB_FULLCHARGE_CAPACITY,
59 	    SENSOR_PERCENT,	 "FullChargeCapacity" },
60 	{ HUP_BATTERY,	HUB_CHARGING,
61 	    SENSOR_INDICATOR,	 "Charging" },
62 	{ HUP_BATTERY,	HUB_DISCHARGING,
63 	    SENSOR_INDICATOR,	 "Discharging" },
64 	{ HUP_BATTERY,	HUB_BATTERY_PRESENT,
65 	    SENSOR_INDICATOR,	 "BatteryPresent" },
66 	{ HUP_POWER,	HUP_SHUTDOWN_IMMINENT,
67 	    SENSOR_INDICATOR,	 "ShutdownImminent" },
68 	{ HUP_BATTERY,	HUB_AC_PRESENT,
69 	    SENSOR_INDICATOR,	 "ACPresent" },
70 	{ HUP_BATTERY,	HUB_ATRATE_TIMETOFULL,
71 	    SENSOR_TIMEDELTA,	 "AtRateTimeToFull" }
72 };
73 
74 struct upd_report {
75 	size_t		size;
76 	int		enabled;
77 };
78 
79 struct upd_sensor {
80 	struct ksensor		ksensor;
81 	struct hid_item		hitem;
82 	int			attached;
83 };
84 
85 struct upd_softc {
86 	struct uhidev		 sc_hdev;
87 	int			 sc_num_sensors;
88 	u_int			 sc_max_repid;
89 	u_int			 sc_max_sensors;
90 
91 	/* sensor framework */
92 	struct ksensordev	 sc_sensordev;
93 	struct sensor_task	*sc_sensortask;
94 	struct upd_report	*sc_reports;
95 	struct upd_sensor	*sc_sensors;
96 };
97 
98 int  upd_match(struct device *, void *, void *);
99 void upd_attach(struct device *, struct device *, void *);
100 int  upd_detach(struct device *, int);
101 
102 void upd_refresh(void *);
103 void upd_update_sensors(struct upd_softc *, uint8_t *, unsigned int, int);
104 void upd_intr(struct uhidev *, void *, uint);
105 struct upd_usage_entry *upd_lookup_usage_entry(const struct hid_item *);
106 struct upd_sensor *upd_lookup_sensor(struct upd_softc *, int, int);
107 
108 struct cfdriver upd_cd = {
109 	NULL, "upd", DV_DULL
110 };
111 
112 const struct cfattach upd_ca = {
113 	sizeof(struct upd_softc),
114 	upd_match,
115 	upd_attach,
116 	upd_detach
117 };
118 
119 int
120 upd_match(struct device *parent, void *match, void *aux)
121 {
122 	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
123 	int			  size;
124 	void			 *desc;
125 	struct hid_data		 *hdata;
126 	struct hid_item		  item;
127 	int			  ret = UMATCH_NONE;
128 
129 	if (uha->reportid != UHIDEV_CLAIM_ALLREPORTID)
130 		return (ret);
131 
132 	DPRINTF(("upd: vendor=0x%04x, product=0x%04x\n", uha->uaa->vendor,
133 	    uha->uaa->product));
134 
135 	/*
136 	 * look for at least one sensor of our table
137 	 */
138 	uhidev_get_report_desc(uha->parent, &desc, &size);
139 	for (hdata = hid_start_parse(desc, size, hid_feature);
140 	     hid_get_item(hdata, &item); ) {
141 		if (upd_lookup_usage_entry(&item) != NULL) {
142 			ret = UMATCH_VENDOR_PRODUCT;
143 			break;
144 		}
145 	}
146 	hid_end_parse(hdata);
147 
148 	return (ret);
149 }
150 
151 void
152 upd_attach(struct device *parent, struct device *self, void *aux)
153 {
154 	struct upd_softc	 *sc = (struct upd_softc *)self;
155 	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
156 	struct hid_item		  item;
157 	struct hid_data		 *hdata;
158 	struct upd_usage_entry	 *entry;
159 	struct upd_sensor	 *sensor;
160 	int			  size;
161 	void			 *desc;
162 
163 	sc->sc_hdev.sc_intr = upd_intr;
164 	sc->sc_hdev.sc_parent = uha->parent;
165 	sc->sc_reports = NULL;
166 	sc->sc_sensors = NULL;
167 	sc->sc_max_sensors = nitems(upd_usage_table);
168 
169 	strlcpy(sc->sc_sensordev.xname, DEVNAME(sc),
170 	    sizeof(sc->sc_sensordev.xname));
171 
172 	sc->sc_max_repid = uha->parent->sc_nrepid;
173 	DPRINTF(("\nupd: devname=%s sc_max_repid=%d\n",
174 	    DEVNAME(sc), sc->sc_max_repid));
175 
176 	sc->sc_reports = mallocarray(sc->sc_max_repid,
177 	    sizeof(struct upd_report), M_USBDEV, M_WAITOK | M_ZERO);
178 	sc->sc_sensors = mallocarray(sc->sc_max_sensors,
179 	    sizeof(struct upd_sensor), M_USBDEV, M_WAITOK | M_ZERO);
180 	size = sc->sc_max_sensors * sizeof(struct upd_sensor);
181 	sc->sc_num_sensors = 0;
182 	uhidev_get_report_desc(uha->parent, &desc, &size);
183 	for (hdata = hid_start_parse(desc, size, hid_feature);
184 	     hid_get_item(hdata, &item) &&
185 	     sc->sc_num_sensors < sc->sc_max_sensors; ) {
186 		DPRINTF(("upd: repid=%d\n", item.report_ID));
187 		if (item.kind != hid_feature ||
188 		    item.report_ID < 0 ||
189 		    item.report_ID >= sc->sc_max_repid)
190 			continue;
191 
192 		if ((entry = upd_lookup_usage_entry(&item)) == NULL)
193 			continue;
194 
195 		/* filter repeated usages, avoid duplicated sensors */
196 		sensor = upd_lookup_sensor(sc, entry->usage_pg,
197 		    entry->usage_id);
198 		if (sensor != NULL)
199 			continue;
200 
201 		sensor = &sc->sc_sensors[sc->sc_num_sensors];
202 		memcpy(&sensor->hitem, &item, sizeof(struct hid_item));
203 		strlcpy(sensor->ksensor.desc, entry->usage_name,
204 		    sizeof(sensor->ksensor.desc));
205 		sensor->ksensor.type = entry->senstype;
206 		sensor->ksensor.flags |= SENSOR_FINVALID;
207 		sensor->ksensor.status = SENSOR_S_UNKNOWN;
208 		sensor->ksensor.value = 0;
209 		sensor_attach(&sc->sc_sensordev, &sensor->ksensor);
210 		sensor->attached = 1;
211 		sc->sc_num_sensors++;
212 
213 		if (sc->sc_reports[item.report_ID].enabled)
214 			continue;
215 
216 		sc->sc_reports[item.report_ID].size = hid_report_size(desc,
217 		    size, item.kind, item.report_ID);
218 		sc->sc_reports[item.report_ID].enabled = 1;
219 	}
220 	hid_end_parse(hdata);
221 	DPRINTF(("upd: sc_num_sensors=%d\n", sc->sc_num_sensors));
222 
223 	sc->sc_sensortask = sensor_task_register(sc, upd_refresh, 6);
224 	if (sc->sc_sensortask == NULL) {
225 		printf(", unable to register update task\n");
226 		return;
227 	}
228 	sensordev_install(&sc->sc_sensordev);
229 
230 	printf("\n");
231 
232 	DPRINTF(("upd_attach: complete\n"));
233 }
234 
235 int
236 upd_detach(struct device *self, int flags)
237 {
238 	struct upd_softc	*sc = (struct upd_softc *)self;
239 	struct upd_sensor	*sensor;
240 	int			 i;
241 
242 	if (sc->sc_sensortask != NULL) {
243 		wakeup(&sc->sc_sensortask);
244 		sensor_task_unregister(sc->sc_sensortask);
245 	}
246 
247 	sensordev_deinstall(&sc->sc_sensordev);
248 
249 	for (i = 0; i < sc->sc_num_sensors; i++) {
250 		sensor = &sc->sc_sensors[i];
251 		if (sensor->attached)
252 			sensor_detach(&sc->sc_sensordev, &sensor->ksensor);
253 		DPRINTF(("upd_detach: %s\n", sensor->ksensor.desc));
254 	}
255 
256 	free(sc->sc_reports, M_USBDEV, 0);
257 	free(sc->sc_sensors, M_USBDEV, 0);
258 	DPRINTF(("upd_detach: complete\n"));
259 	return (0);
260 }
261 
262 void
263 upd_refresh(void *arg)
264 {
265 	struct upd_softc	*sc = (struct upd_softc *)arg;
266 	struct upd_report	*report;
267 	uint8_t			buf[256];
268 	int			repid, actlen;
269 
270 	for (repid = 0; repid < sc->sc_max_repid; repid++) {
271 		report = &sc->sc_reports[repid];
272 		if (!report->enabled)
273 			continue;
274 
275 		memset(buf, 0x0, sizeof(buf));
276 		actlen = uhidev_get_report(sc->sc_hdev.sc_parent,
277 		    UHID_FEATURE_REPORT, repid, buf, report->size);
278 
279 		if (actlen == -1) {
280 			DPRINTF(("upd: failed to get report id=%02x\n", repid));
281 			continue;
282 		}
283 
284 		/* Deal with buggy firmwares. */
285 		if (actlen < report->size)
286 			report->size = actlen;
287 
288 		upd_update_sensors(sc, buf, report->size, repid);
289 	}
290 }
291 
292 struct upd_usage_entry *
293 upd_lookup_usage_entry(const struct hid_item *hitem)
294 {
295 	struct upd_usage_entry	*entry = NULL;
296 	int			 i;
297 
298 	for (i = 0; i < nitems(upd_usage_table); i++) {
299 		entry = &upd_usage_table[i];
300 		if (entry->usage_pg == HID_GET_USAGE_PAGE(hitem->usage) &&
301 		    entry->usage_id == HID_GET_USAGE(hitem->usage))
302 			return (entry);
303 	}
304 	return (NULL);
305 }
306 
307 struct upd_sensor *
308 upd_lookup_sensor(struct upd_softc *sc, int page, int usage)
309 {
310 	struct upd_sensor	*sensor = NULL;
311 	int			 i;
312 
313 	for (i = 0; i < sc->sc_num_sensors; i++) {
314 		sensor = &sc->sc_sensors[i];
315 		if (page == HID_GET_USAGE_PAGE(sensor->hitem.usage) &&
316 		    usage == HID_GET_USAGE(sensor->hitem.usage))
317 			return (sensor);
318 	}
319 	return (NULL);
320 }
321 
322 void
323 upd_update_sensors(struct upd_softc *sc, uint8_t *buf, unsigned int len,
324     int repid)
325 {
326 	struct upd_sensor	*sensor;
327 	ulong			hdata, batpres;
328 	ulong 			adjust;
329 	int			i;
330 
331 	sensor = upd_lookup_sensor(sc, HUP_BATTERY, HUB_BATTERY_PRESENT);
332 	batpres = sensor ? sensor->ksensor.value : -1;
333 
334 	for (i = 0; i < sc->sc_num_sensors; i++) {
335 		sensor = &sc->sc_sensors[i];
336 		if (!(sensor->hitem.report_ID == repid && sensor->attached))
337 			continue;
338 
339 		/* invalidate battery dependent sensors */
340 		if (HID_GET_USAGE_PAGE(sensor->hitem.usage) == HUP_BATTERY &&
341 		    batpres <= 0) {
342 			/* exception to the battery sensor itself */
343 			if (HID_GET_USAGE(sensor->hitem.usage) !=
344 			    HUB_BATTERY_PRESENT) {
345 				sensor->ksensor.status = SENSOR_S_UNKNOWN;
346 				sensor->ksensor.flags |= SENSOR_FINVALID;
347 				continue;
348 			}
349 		}
350 
351 		switch (HID_GET_USAGE(sensor->hitem.usage)) {
352 		case HUB_REL_STATEOF_CHARGE:
353 		case HUB_ABS_STATEOF_CHARGE:
354 		case HUB_REM_CAPACITY:
355 		case HUB_FULLCHARGE_CAPACITY:
356 			adjust = 1000; /* scale adjust */
357 			break;
358 		default:
359 			adjust = 1; /* no scale adjust */
360 			break;
361 		}
362 
363 		hdata = hid_get_data(buf, len, &sensor->hitem.loc);
364 
365 		sensor->ksensor.value = hdata * adjust;
366 		sensor->ksensor.status = SENSOR_S_OK;
367 		sensor->ksensor.flags &= ~SENSOR_FINVALID;
368 		DPRINTF(("%s: hidget data: %lu\n", DEVNAME(sc), hdata));
369 	}
370 }
371 
372 
373 void
374 upd_intr(struct uhidev *uh, void *p, uint len)
375 {
376 	/* noop */
377 }
378