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