xref: /openbsd-src/sys/dev/acpi/acpibat.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /* $OpenBSD: acpibat.c,v 1.67 2018/07/01 19:40:49 mlarkin Exp $ */
2 /*
3  * Copyright (c) 2005 Marco Peereboom <marco@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/device.h>
21 #include <sys/malloc.h>
22 #include <sys/sensors.h>
23 
24 #include <machine/apmvar.h>
25 
26 #include <dev/acpi/acpireg.h>
27 #include <dev/acpi/acpivar.h>
28 #include <dev/acpi/acpidev.h>
29 #include <dev/acpi/amltypes.h>
30 #include <dev/acpi/dsdt.h>
31 
32 int	acpibat_match(struct device *, void *, void *);
33 void	acpibat_attach(struct device *, struct device *, void *);
34 
35 struct cfattach acpibat_ca = {
36 	sizeof(struct acpibat_softc), acpibat_match, acpibat_attach
37 };
38 
39 struct cfdriver acpibat_cd = {
40 	NULL, "acpibat", DV_DULL
41 };
42 
43 const char *acpibat_hids[] = {
44 	ACPI_DEV_CMB,
45 	NULL
46 };
47 
48 void	acpibat_monitor(struct acpibat_softc *);
49 void	acpibat_refresh(void *);
50 int	acpibat_getbix(struct acpibat_softc *);
51 int	acpibat_getbst(struct acpibat_softc *);
52 int	acpibat_notify(struct aml_node *, int, void *);
53 
54 int
55 acpibat_match(struct device *parent, void *match, void *aux)
56 {
57 	struct acpi_attach_args	*aa = aux;
58 	struct cfdata		*cf = match;
59 
60 	if (((struct acpi_softc *)parent)->sc_havesbs)
61 		return (0);
62 
63 	/* sanity */
64 	return (acpi_matchhids(aa, acpibat_hids, cf->cf_driver->cd_name));
65 }
66 
67 void
68 acpibat_attach(struct device *parent, struct device *self, void *aux)
69 {
70 	struct acpibat_softc	*sc = (struct acpibat_softc *)self;
71 	struct acpi_attach_args	*aa = aux;
72 	int64_t			sta;
73 
74 	sc->sc_acpi = (struct acpi_softc *)parent;
75 	sc->sc_devnode = aa->aaa_node;
76 
77 	if (aml_evalinteger(sc->sc_acpi, sc->sc_devnode, "_STA", 0, NULL, &sta)) {
78 		dnprintf(10, "%s: no _STA\n", DEVNAME(sc));
79 		return;
80 	}
81 
82 	if ((sta & STA_BATTERY) != 0) {
83 		sc->sc_bat_present = 1;
84 		acpibat_getbix(sc);
85 		acpibat_getbst(sc);
86 
87 		printf(": %s", sc->sc_devnode->name);
88 		if (sc->sc_bix.bix_model[0])
89 			printf(" model \"%s\"", sc->sc_bix.bix_model);
90 		if (sc->sc_bix.bix_serial[0])
91 			printf(" serial %s", sc->sc_bix.bix_serial);
92 		if (sc->sc_bix.bix_type[0])
93 			printf(" type %s", sc->sc_bix.bix_type);
94 		if (sc->sc_bix.bix_oem[0])
95 			printf(" oem \"%s\"", sc->sc_bix.bix_oem);
96 
97 		printf("\n");
98 	} else {
99 		sc->sc_bat_present = 0;
100 		printf(": %s not present\n", sc->sc_devnode->name);
101 	}
102 
103 	/* create sensors */
104 	acpibat_monitor(sc);
105 
106 	/* populate sensors */
107 	acpibat_refresh(sc);
108 
109 	aml_register_notify(sc->sc_devnode, aa->aaa_dev,
110 	    acpibat_notify, sc, ACPIDEV_POLL);
111 }
112 
113 void
114 acpibat_monitor(struct acpibat_softc *sc)
115 {
116 	int			type;
117 
118 	/* assume _BIF/_BIX and _BST have been called */
119 	strlcpy(sc->sc_sensdev.xname, DEVNAME(sc),
120 	    sizeof(sc->sc_sensdev.xname));
121 
122 	type = sc->sc_bix.bix_power_unit ? SENSOR_AMPHOUR : SENSOR_WATTHOUR;
123 
124 	strlcpy(sc->sc_sens[0].desc, "last full capacity",
125 	    sizeof(sc->sc_sens[0].desc));
126 	sc->sc_sens[0].type = type;
127 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[0]);
128 	sc->sc_sens[0].value = sc->sc_bix.bix_last_capacity * 1000;
129 
130 	strlcpy(sc->sc_sens[1].desc, "warning capacity",
131 	    sizeof(sc->sc_sens[1].desc));
132 	sc->sc_sens[1].type = type;
133 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[1]);
134 	sc->sc_sens[1].value = sc->sc_bix.bix_warning * 1000;
135 
136 	strlcpy(sc->sc_sens[2].desc, "low capacity",
137 	    sizeof(sc->sc_sens[2].desc));
138 	sc->sc_sens[2].type = type;
139 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[2]);
140 	sc->sc_sens[2].value = sc->sc_bix.bix_low * 1000;
141 
142 	strlcpy(sc->sc_sens[3].desc, "voltage", sizeof(sc->sc_sens[3].desc));
143 	sc->sc_sens[3].type = SENSOR_VOLTS_DC;
144 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[3]);
145 	sc->sc_sens[3].value = sc->sc_bix.bix_voltage * 1000;
146 
147 	strlcpy(sc->sc_sens[4].desc, "battery unknown",
148 	    sizeof(sc->sc_sens[4].desc));
149 	sc->sc_sens[4].type = SENSOR_INTEGER;
150 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[4]);
151 	sc->sc_sens[4].value = sc->sc_bst.bst_state;
152 
153 	strlcpy(sc->sc_sens[5].desc, "rate", sizeof(sc->sc_sens[5].desc));
154 	sc->sc_sens[5].type =
155 		sc->sc_bix.bix_power_unit ? SENSOR_AMPS : SENSOR_WATTS;
156 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[5]);
157 	sc->sc_sens[5].value = sc->sc_bst.bst_rate * 1000;
158 
159 	strlcpy(sc->sc_sens[6].desc, "remaining capacity",
160 	    sizeof(sc->sc_sens[6].desc));
161 	sc->sc_sens[6].type = type;
162 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[6]);
163 	sc->sc_sens[6].value = sc->sc_bix.bix_capacity * 1000;
164 
165 	strlcpy(sc->sc_sens[7].desc, "current voltage",
166 	    sizeof(sc->sc_sens[7].desc));
167 	sc->sc_sens[7].type = SENSOR_VOLTS_DC;
168 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[7]);
169 	sc->sc_sens[7].value = sc->sc_bix.bix_voltage * 1000;
170 
171 	strlcpy(sc->sc_sens[8].desc, "design capacity",
172 	    sizeof(sc->sc_sens[8].desc));
173 	sc->sc_sens[8].type = type;
174 	sensor_attach(&sc->sc_sensdev, &sc->sc_sens[8]);
175 	sc->sc_sens[8].value = sc->sc_bix.bix_capacity * 1000;
176 
177 	if (!sc->sc_use_bif) {
178 		strlcpy(sc->sc_sens[9].desc, "discharge cycles",
179 		    sizeof(sc->sc_sens[9].desc));
180 		sc->sc_sens[9].type = SENSOR_INTEGER;
181 		sensor_attach(&sc->sc_sensdev, &sc->sc_sens[9]);
182 		sc->sc_sens[9].value = sc->sc_bix.bix_cycle_count;
183 	}
184 
185 	sensordev_install(&sc->sc_sensdev);
186 }
187 
188 void
189 acpibat_refresh(void *arg)
190 {
191 	struct acpibat_softc	*sc = arg;
192 	int			i;
193 
194 	dnprintf(30, "%s: %s: refresh\n", DEVNAME(sc),
195 	    sc->sc_devnode->name);
196 
197 	if (!sc->sc_bat_present) {
198 		for (i = 0; i < nitems(sc->sc_sens); i++) {
199 			sc->sc_sens[i].value = 0;
200 			sc->sc_sens[i].status = SENSOR_S_UNSPEC;
201 			sc->sc_sens[i].flags = SENSOR_FINVALID;
202 		}
203 		/* override state */
204 		strlcpy(sc->sc_sens[4].desc, "battery removed",
205 		    sizeof(sc->sc_sens[4].desc));
206 		return;
207 	}
208 
209 	/* _BIF/_BIX values are static, sensor 0..3 */
210 	if (sc->sc_bix.bix_last_capacity == BIX_UNKNOWN) {
211 		sc->sc_sens[0].value = 0;
212 		sc->sc_sens[0].status = SENSOR_S_UNKNOWN;
213 		sc->sc_sens[0].flags = SENSOR_FUNKNOWN;
214 	} else {
215 		sc->sc_sens[0].value = sc->sc_bix.bix_last_capacity * 1000;
216 		sc->sc_sens[0].status = SENSOR_S_UNSPEC;
217 		sc->sc_sens[0].flags = 0;
218 	}
219 	sc->sc_sens[1].value = sc->sc_bix.bix_warning * 1000;
220 	sc->sc_sens[1].flags = 0;
221 	sc->sc_sens[2].value = sc->sc_bix.bix_low * 1000;
222 	sc->sc_sens[2].flags = 0;
223 	if (sc->sc_bix.bix_voltage == BIX_UNKNOWN) {
224 		sc->sc_sens[3].value = 0;
225 		sc->sc_sens[3].status = SENSOR_S_UNKNOWN;
226 		sc->sc_sens[3].flags = SENSOR_FUNKNOWN;
227 	} else {
228 		sc->sc_sens[3].value = sc->sc_bix.bix_voltage * 1000;
229 		sc->sc_sens[3].status = SENSOR_S_UNSPEC;
230 		sc->sc_sens[3].flags = 0;
231 	}
232 
233 	/* _BST values are dynamic, sensor 4..7 */
234 	sc->sc_sens[4].status = SENSOR_S_OK;
235 	sc->sc_sens[4].flags = 0;
236 	if (sc->sc_bix.bix_last_capacity == BIX_UNKNOWN ||
237 	    sc->sc_bst.bst_capacity == BST_UNKNOWN) {
238 		sc->sc_sens[4].status = SENSOR_S_UNKNOWN;
239 		sc->sc_sens[4].flags = SENSOR_FUNKNOWN;
240 		strlcpy(sc->sc_sens[4].desc, "battery unknown",
241 		    sizeof(sc->sc_sens[4].desc));
242 	} else if (sc->sc_bst.bst_capacity >= sc->sc_bix.bix_last_capacity)
243 		strlcpy(sc->sc_sens[4].desc, "battery full",
244 		    sizeof(sc->sc_sens[4].desc));
245 	else if (sc->sc_bst.bst_state & BST_DISCHARGE)
246 		strlcpy(sc->sc_sens[4].desc, "battery discharging",
247 		    sizeof(sc->sc_sens[4].desc));
248 	else if (sc->sc_bst.bst_state & BST_CHARGE)
249 		strlcpy(sc->sc_sens[4].desc, "battery charging",
250 		    sizeof(sc->sc_sens[4].desc));
251 	else if (sc->sc_bst.bst_state & BST_CRITICAL) {
252 		strlcpy(sc->sc_sens[4].desc, "battery critical",
253 		    sizeof(sc->sc_sens[4].desc));
254 		sc->sc_sens[4].status = SENSOR_S_CRIT;
255 	} else
256 		strlcpy(sc->sc_sens[4].desc, "battery idle",
257 		    sizeof(sc->sc_sens[4].desc));
258 	sc->sc_sens[4].value = sc->sc_bst.bst_state;
259 
260 	if (sc->sc_bst.bst_rate == BST_UNKNOWN) {
261 		sc->sc_sens[5].value = 0;
262 		sc->sc_sens[5].status = SENSOR_S_UNKNOWN;
263 		sc->sc_sens[5].flags = SENSOR_FUNKNOWN;
264 	} else {
265 		sc->sc_sens[5].value = sc->sc_bst.bst_rate * 1000;
266 		sc->sc_sens[5].status = SENSOR_S_UNSPEC;
267 		sc->sc_sens[5].flags = 0;
268 	}
269 
270 	if (sc->sc_bst.bst_capacity == BST_UNKNOWN) {
271 		sc->sc_sens[6].value = 0;
272 		sc->sc_sens[6].status = SENSOR_S_UNKNOWN;
273 		sc->sc_sens[6].flags = SENSOR_FUNKNOWN;
274 	} else {
275 		sc->sc_sens[6].value = sc->sc_bst.bst_capacity * 1000;
276 		sc->sc_sens[6].flags = 0;
277 
278 		if (sc->sc_bst.bst_capacity < sc->sc_bix.bix_low)
279 			/* XXX we should shutdown the system */
280 			sc->sc_sens[6].status = SENSOR_S_CRIT;
281 		else if (sc->sc_bst.bst_capacity < sc->sc_bix.bix_warning)
282 			sc->sc_sens[6].status = SENSOR_S_WARN;
283 		else
284 			sc->sc_sens[6].status = SENSOR_S_OK;
285 	}
286 
287 	if (sc->sc_bst.bst_voltage == BST_UNKNOWN) {
288 		sc->sc_sens[7].value = 0;
289 		sc->sc_sens[7].status = SENSOR_S_UNKNOWN;
290 		sc->sc_sens[7].flags = SENSOR_FUNKNOWN;
291 	} else {
292 		sc->sc_sens[7].value = sc->sc_bst.bst_voltage * 1000;
293 		sc->sc_sens[7].status = SENSOR_S_UNSPEC;
294 		sc->sc_sens[7].flags = 0;
295 	}
296 
297 	if (sc->sc_bix.bix_capacity == BIX_UNKNOWN) {
298 		sc->sc_sens[8].value = 0;
299 		sc->sc_sens[8].status = SENSOR_S_UNKNOWN;
300 		sc->sc_sens[8].flags = SENSOR_FUNKNOWN;
301 	} else {
302 		sc->sc_sens[8].value = sc->sc_bix.bix_capacity * 1000;
303 		sc->sc_sens[8].status = SENSOR_S_UNSPEC;
304 		sc->sc_sens[8].flags = 0;
305 	}
306 
307 	if (!sc->sc_use_bif) {
308 		if (sc->sc_bix.bix_capacity == BIX_UNKNOWN) {
309 			sc->sc_sens[9].value = 0;
310 			sc->sc_sens[9].status = SENSOR_S_UNKNOWN;
311 			sc->sc_sens[9].flags = SENSOR_FUNKNOWN;
312 		} else {
313 			sc->sc_sens[9].value = sc->sc_bix.bix_cycle_count;
314 			sc->sc_sens[9].status = SENSOR_S_UNSPEC;
315 			sc->sc_sens[9].flags = 0;
316 		}
317 	}
318 
319 	acpi_record_event(sc->sc_acpi, APM_POWER_CHANGE);
320 }
321 
322 int
323 acpibat_getbix(struct acpibat_softc *sc)
324 {
325 	struct aml_value	res;
326 	int			rv = EINVAL;
327 	int			n = 0;
328 
329 	if (!sc->sc_bat_present) {
330 		memset(&sc->sc_bix, 0, sizeof(sc->sc_bix));
331 		return (0);
332 	}
333 
334 	sc->sc_use_bif = 1;
335 
336 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BIX", 0, NULL,
337 	    &res) == 0) {
338 		if (res.length >= 20)
339 			sc->sc_use_bif = 0;
340 		else
341 			dnprintf(10, "%s: invalid _BIX (%d < 20)\n",
342 			    DEVNAME(sc), res.length);
343 	}
344 
345 	if (sc->sc_use_bif) {
346 		if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BIF", 0, NULL,
347 		    &res)) {
348 			dnprintf(10, "%s: no _BIX or _BIF\n", DEVNAME(sc));
349 			goto out;
350 		}
351 
352 		if (res.length != 13) {
353 			dnprintf(10, "%s: invalid _BIF (%d != 13)\n",
354 			    DEVNAME(sc), res.length);
355 			goto out;
356 		}
357 	}
358 
359 	if (!sc->sc_use_bif)
360 		sc->sc_bix.bix_revision = aml_val2int(res.v_package[n++]);
361 
362 	sc->sc_bix.bix_power_unit = aml_val2int(res.v_package[n++]);
363 	sc->sc_bix.bix_capacity = aml_val2int(res.v_package[n++]);
364 	sc->sc_bix.bix_last_capacity = aml_val2int(res.v_package[n++]);
365 	sc->sc_bix.bix_technology = aml_val2int(res.v_package[n++]);
366 	sc->sc_bix.bix_voltage = aml_val2int(res.v_package[n++]);
367 	sc->sc_bix.bix_warning = aml_val2int(res.v_package[n++]);
368 	sc->sc_bix.bix_low = aml_val2int(res.v_package[n++]);
369 
370 	if (!sc->sc_use_bif) {
371 		sc->sc_bix.bix_cycle_count = aml_val2int(res.v_package[n++]);
372 		sc->sc_bix.bix_accuracy = aml_val2int(res.v_package[n++]);
373 		sc->sc_bix.bix_max_sample = aml_val2int(res.v_package[n++]);
374 		sc->sc_bix.bix_min_sample = aml_val2int(res.v_package[n++]);
375 		sc->sc_bix.bix_max_avg = aml_val2int(res.v_package[n++]);
376 		sc->sc_bix.bix_min_avg = aml_val2int(res.v_package[n++]);
377 	}
378 
379 	sc->sc_bix.bix_cap_granu1 = aml_val2int(res.v_package[n++]);
380 	sc->sc_bix.bix_cap_granu2 = aml_val2int(res.v_package[n++]);
381 
382 	strlcpy(sc->sc_bix.bix_model, aml_val_to_string(res.v_package[n++]),
383 		sizeof(sc->sc_bix.bix_model));
384 	strlcpy(sc->sc_bix.bix_serial, aml_val_to_string(res.v_package[n++]),
385 		sizeof(sc->sc_bix.bix_serial));
386 	strlcpy(sc->sc_bix.bix_type, aml_val_to_string(res.v_package[n++]),
387 		sizeof(sc->sc_bix.bix_type));
388 	strlcpy(sc->sc_bix.bix_oem, aml_val_to_string(res.v_package[n++]),
389 		sizeof(sc->sc_bix.bix_oem));
390 
391 	if (!sc->sc_use_bif)
392 		dnprintf(60, "revision: %u ", sc->sc_bix.bix_revision);
393 
394 	dnprintf(60, "power_unit: %u capacity: %u last_cap: %u "
395 	    "tech: %u volt: %u warn: %u low: %u ",
396 	    sc->sc_bix.bix_power_unit,
397 	    sc->sc_bix.bix_capacity,
398 	    sc->sc_bix.bix_last_capacity,
399 	    sc->sc_bix.bix_technology,
400 	    sc->sc_bix.bix_voltage,
401 	    sc->sc_bix.bix_warning,
402 	    sc->sc_bix.bix_low);
403 
404 	if (!sc->sc_use_bif)
405 		dnprintf(60, "cycles: %u accuracy: %u max_sample: %u "
406 		    "min_sample: %u max_avg: %u min_avg: %u ",
407 		    sc->sc_bix.bix_cycle_count,
408 		    sc->sc_bix.bix_accuracy,
409 		    sc->sc_bix.bix_max_sample,
410 		    sc->sc_bix.bix_min_sample,
411 		    sc->sc_bix.bix_max_avg,
412 		    sc->sc_bix.bix_min_avg);
413 
414 	dnprintf(60, "gran1: %u gran2: %d model: %s serial: %s type: %s "
415 	    "oem: %s\n",
416 	    sc->sc_bix.bix_cap_granu1,
417 	    sc->sc_bix.bix_cap_granu2,
418 	    sc->sc_bix.bix_model,
419 	    sc->sc_bix.bix_serial,
420 	    sc->sc_bix.bix_type,
421 	    sc->sc_bix.bix_oem);
422 
423 	rv = 0;
424 out:
425 	aml_freevalue(&res);
426 	return (rv);
427 }
428 
429 int
430 acpibat_getbst(struct acpibat_softc *sc)
431 {
432 	struct aml_value	res;
433 	int			rv = EINVAL;
434 
435 	if (!sc->sc_bat_present) {
436 		memset(&sc->sc_bst, 0, sizeof(sc->sc_bst));
437 		return (0);
438 	}
439 
440 	if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BST", 0, NULL, &res)) {
441 		dnprintf(10, "%s: no _BST\n", DEVNAME(sc));
442 		goto out;
443 	}
444 
445 	if (res.length != 4) {
446 		dnprintf(10, "%s: invalid _BST, battery status not saved\n",
447 		    DEVNAME(sc));
448 		goto out;
449 	}
450 
451 	sc->sc_bst.bst_state = aml_val2int(res.v_package[0]);
452 	sc->sc_bst.bst_rate = aml_val2int(res.v_package[1]);
453 	sc->sc_bst.bst_capacity = aml_val2int(res.v_package[2]);
454 	sc->sc_bst.bst_voltage = aml_val2int(res.v_package[3]);
455 
456 	dnprintf(60, "state: %u rate: %u cap: %u volt: %u ",
457 	    sc->sc_bst.bst_state,
458 	    sc->sc_bst.bst_rate,
459 	    sc->sc_bst.bst_capacity,
460 	    sc->sc_bst.bst_voltage);
461 
462 	rv = 0;
463 out:
464 	aml_freevalue(&res);
465 	return (rv);
466 }
467 
468 /*
469  * XXX it has been observed that some systems do not propagate battery
470  * insertion events up to the driver.  What seems to happen is that DSDT
471  * does receive an interrupt however the originator bit is not set.
472  * This seems to happen when one inserts a 100% full battery.  Removal
473  * of the power cord or insertion of a not 100% full battery breaks this
474  * behavior and all events will then be sent upwards.  Currently there
475  * is no known work-around for it.
476  */
477 
478 int
479 acpibat_notify(struct aml_node *node, int notify_type, void *arg)
480 {
481 	struct acpibat_softc	*sc = arg;
482 	int64_t			sta;
483 
484 	dnprintf(10, "acpibat_notify: %.2x %s\n", notify_type,
485 	    sc->sc_devnode->name);
486 
487 	/* Check if installed state of battery has changed */
488 	if (aml_evalinteger(sc->sc_acpi, node, "_STA", 0, NULL, &sta) == 0) {
489 		if (sta & STA_BATTERY)
490 			sc->sc_bat_present = 1;
491 		else
492 			sc->sc_bat_present = 0;
493 	}
494 
495 	switch (notify_type) {
496 	case 0x00:	/* Poll sensors */
497 	case 0x80:	/* _BST changed */
498 		acpibat_getbst(sc);
499 		break;
500 	case 0x81:	/* _BIF/_BIX changed */
501 		acpibat_getbix(sc);
502 		break;
503 	default:
504 		break;
505 	}
506 
507 	acpibat_refresh(sc);
508 
509 	return (0);
510 }
511