1 /*- 2 * Copyright (c) 2000 Munehiro Matsuda 3 * Copyright (c) 2000 Takanori Watanabe 4 * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 * $FreeBSD$ 29 */ 30 31 #include "opt_acpi.h" 32 #include <sys/param.h> 33 #include <sys/kernel.h> 34 #include <sys/module.h> 35 #include <sys/bus.h> 36 #include <sys/ioccom.h> 37 38 #include <machine/bus.h> 39 #include <sys/rman.h> 40 #include <sys/malloc.h> 41 42 #include "acpi.h" 43 #include <dev/acpica/acpivar.h> 44 #include <dev/acpica/acpiio.h> 45 46 MALLOC_DEFINE(M_ACPICMBAT, "acpicmbat", "ACPI control method battery data"); 47 48 /* Number of times to retry initialization before giving up. */ 49 #define ACPI_CMBAT_RETRY_MAX 6 50 51 /* Check the battery once a minute. */ 52 #define CMBAT_POLLRATE (60 * hz) 53 54 /* Hooks for the ACPI CA debugging infrastructure */ 55 #define _COMPONENT ACPI_BATTERY 56 ACPI_MODULE_NAME("BATTERY") 57 58 #define ACPI_BATTERY_BST_CHANGE 0x80 59 #define ACPI_BATTERY_BIF_CHANGE 0x81 60 61 struct acpi_cmbat_softc { 62 device_t dev; 63 64 struct acpi_bif bif; 65 struct acpi_bst bst; 66 struct timespec bif_lastupdated; 67 struct timespec bst_lastupdated; 68 69 int present; 70 int cap; 71 int min; 72 int full_charge_time; 73 int initializing; 74 int phys_unit; 75 }; 76 77 static struct timespec acpi_cmbat_info_lastupdated; 78 ACPI_SERIAL_DECL(cmbat, "ACPI cmbat"); 79 80 /* XXX: devclass_get_maxunit() don't give us the current allocated units. */ 81 static int acpi_cmbat_units = 0; 82 83 static int acpi_cmbat_info_expired(struct timespec *); 84 static void acpi_cmbat_info_updated(struct timespec *); 85 static void acpi_cmbat_get_bst(void *); 86 static void acpi_cmbat_get_bif(void *); 87 static void acpi_cmbat_notify_handler(ACPI_HANDLE, UINT32, void *); 88 static int acpi_cmbat_probe(device_t); 89 static int acpi_cmbat_attach(device_t); 90 static int acpi_cmbat_detach(device_t); 91 static int acpi_cmbat_resume(device_t); 92 static int acpi_cmbat_ioctl(u_long, caddr_t, void *); 93 static int acpi_cmbat_is_bst_valid(struct acpi_bst*); 94 static int acpi_cmbat_is_bif_valid(struct acpi_bif*); 95 static int acpi_cmbat_get_total_battinfo(struct acpi_battinfo *); 96 static void acpi_cmbat_init_battery(void *); 97 98 static device_method_t acpi_cmbat_methods[] = { 99 /* Device interface */ 100 DEVMETHOD(device_probe, acpi_cmbat_probe), 101 DEVMETHOD(device_attach, acpi_cmbat_attach), 102 DEVMETHOD(device_detach, acpi_cmbat_detach), 103 DEVMETHOD(device_resume, acpi_cmbat_resume), 104 105 {0, 0} 106 }; 107 108 static driver_t acpi_cmbat_driver = { 109 "acpi_cmbat", 110 acpi_cmbat_methods, 111 sizeof(struct acpi_cmbat_softc), 112 }; 113 114 static devclass_t acpi_cmbat_devclass; 115 DRIVER_MODULE(acpi_cmbat, acpi, acpi_cmbat_driver, acpi_cmbat_devclass, 0, 0); 116 MODULE_DEPEND(acpi_cmbat, acpi, 1, 1, 1); 117 118 static int 119 acpi_cmbat_info_expired(struct timespec *lastupdated) 120 { 121 struct timespec curtime; 122 123 ACPI_SERIAL_ASSERT(cmbat); 124 125 if (lastupdated == NULL) 126 return (TRUE); 127 if (!timespecisset(lastupdated)) 128 return (TRUE); 129 130 getnanotime(&curtime); 131 timespecsub(&curtime, lastupdated); 132 return (curtime.tv_sec < 0 || 133 curtime.tv_sec > acpi_battery_get_info_expire()); 134 } 135 136 static void 137 acpi_cmbat_info_updated(struct timespec *lastupdated) 138 { 139 140 ACPI_SERIAL_ASSERT(cmbat); 141 142 if (lastupdated != NULL) 143 getnanotime(lastupdated); 144 } 145 146 static void 147 acpi_cmbat_get_bst(void *context) 148 { 149 device_t dev; 150 struct acpi_cmbat_softc *sc; 151 ACPI_STATUS as; 152 ACPI_OBJECT *res; 153 ACPI_HANDLE h; 154 ACPI_BUFFER bst_buffer; 155 156 ACPI_SERIAL_ASSERT(cmbat); 157 158 dev = context; 159 sc = device_get_softc(dev); 160 h = acpi_get_handle(dev); 161 bst_buffer.Pointer = NULL; 162 bst_buffer.Length = ACPI_ALLOCATE_BUFFER; 163 164 if (!acpi_cmbat_info_expired(&sc->bst_lastupdated)) 165 goto end; 166 167 as = AcpiEvaluateObject(h, "_BST", NULL, &bst_buffer); 168 if (ACPI_FAILURE(as)) { 169 ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), 170 "error fetching current battery status -- %s\n", 171 AcpiFormatException(as)); 172 goto end; 173 } 174 175 res = (ACPI_OBJECT *)bst_buffer.Pointer; 176 if (!ACPI_PKG_VALID(res, 4)) { 177 ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), 178 "battery status corrupted\n"); 179 goto end; 180 } 181 182 if (acpi_PkgInt32(res, 0, &sc->bst.state) != 0) 183 goto end; 184 if (acpi_PkgInt32(res, 1, &sc->bst.rate) != 0) 185 goto end; 186 if (acpi_PkgInt32(res, 2, &sc->bst.cap) != 0) 187 goto end; 188 if (acpi_PkgInt32(res, 3, &sc->bst.volt) != 0) 189 goto end; 190 acpi_cmbat_info_updated(&sc->bst_lastupdated); 191 192 end: 193 if (bst_buffer.Pointer != NULL) 194 AcpiOsFree(bst_buffer.Pointer); 195 } 196 197 static void 198 acpi_cmbat_get_bif(void *context) 199 { 200 device_t dev; 201 struct acpi_cmbat_softc *sc; 202 ACPI_STATUS as; 203 ACPI_OBJECT *res; 204 ACPI_HANDLE h; 205 ACPI_BUFFER bif_buffer; 206 207 ACPI_SERIAL_ASSERT(cmbat); 208 209 dev = context; 210 sc = device_get_softc(dev); 211 h = acpi_get_handle(dev); 212 bif_buffer.Pointer = NULL; 213 bif_buffer.Length = ACPI_ALLOCATE_BUFFER; 214 215 if (!acpi_cmbat_info_expired(&sc->bif_lastupdated)) 216 goto end; 217 218 as = AcpiEvaluateObject(h, "_BIF", NULL, &bif_buffer); 219 if (ACPI_FAILURE(as)) { 220 ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), 221 "error fetching current battery info -- %s\n", 222 AcpiFormatException(as)); 223 goto end; 224 } 225 226 res = (ACPI_OBJECT *)bif_buffer.Pointer; 227 if (!ACPI_PKG_VALID(res, 13)) { 228 ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), 229 "battery info corrupted\n"); 230 goto end; 231 } 232 233 if (acpi_PkgInt32(res, 0, &sc->bif.units) != 0) 234 goto end; 235 if (acpi_PkgInt32(res, 1, &sc->bif.dcap) != 0) 236 goto end; 237 if (acpi_PkgInt32(res, 2, &sc->bif.lfcap) != 0) 238 goto end; 239 if (acpi_PkgInt32(res, 3, &sc->bif.btech) != 0) 240 goto end; 241 if (acpi_PkgInt32(res, 4, &sc->bif.dvol) != 0) 242 goto end; 243 if (acpi_PkgInt32(res, 5, &sc->bif.wcap) != 0) 244 goto end; 245 if (acpi_PkgInt32(res, 6, &sc->bif.lcap) != 0) 246 goto end; 247 if (acpi_PkgInt32(res, 7, &sc->bif.gra1) != 0) 248 goto end; 249 if (acpi_PkgInt32(res, 8, &sc->bif.gra2) != 0) 250 goto end; 251 if (acpi_PkgStr(res, 9, sc->bif.model, ACPI_CMBAT_MAXSTRLEN) != 0) 252 goto end; 253 if (acpi_PkgStr(res, 10, sc->bif.serial, ACPI_CMBAT_MAXSTRLEN) != 0) 254 goto end; 255 if (acpi_PkgStr(res, 11, sc->bif.type, ACPI_CMBAT_MAXSTRLEN) != 0) 256 goto end; 257 if (acpi_PkgStr(res, 12, sc->bif.oeminfo, ACPI_CMBAT_MAXSTRLEN) != 0) 258 goto end; 259 acpi_cmbat_info_updated(&sc->bif_lastupdated); 260 261 end: 262 if (bif_buffer.Pointer != NULL) 263 AcpiOsFree(bif_buffer.Pointer); 264 } 265 266 static void 267 acpi_cmbat_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) 268 { 269 device_t dev; 270 struct acpi_cmbat_softc *sc; 271 272 dev = (device_t)context; 273 sc = device_get_softc(dev); 274 275 acpi_UserNotify("CMBAT", h, notify); 276 277 /* 278 * Clear the appropriate last updated time. The next call to retrieve 279 * the battery status will get the new value for us. We don't need to 280 * acquire a lock since we are only clearing the time stamp and since 281 * calling _BST/_BIF can trigger a notify, we could deadlock also. 282 */ 283 switch (notify) { 284 case ACPI_NOTIFY_DEVICE_CHECK: 285 case ACPI_BATTERY_BST_CHANGE: 286 timespecclear(&sc->bst_lastupdated); 287 break; 288 case ACPI_NOTIFY_BUS_CHECK: 289 case ACPI_BATTERY_BIF_CHANGE: 290 timespecclear(&sc->bif_lastupdated); 291 break; 292 default: 293 break; 294 } 295 } 296 297 static int 298 acpi_cmbat_probe(device_t dev) 299 { 300 static char *cmbat_ids[] = { "PNP0C0A", NULL }; 301 302 if (acpi_disabled("cmbat") || 303 ACPI_ID_PROBE(device_get_parent(dev), dev, cmbat_ids) == NULL) 304 return (ENXIO); 305 306 device_set_desc(dev, "Control Method Battery"); 307 return (0); 308 } 309 310 static int 311 acpi_cmbat_attach(device_t dev) 312 { 313 int error; 314 ACPI_HANDLE handle; 315 struct acpi_cmbat_softc *sc; 316 317 sc = device_get_softc(dev); 318 handle = acpi_get_handle(dev); 319 sc->dev = dev; 320 321 /* 322 * Install a system notify handler in addition to the device notify. 323 * Toshiba notebook uses this alternate notify for its battery. 324 */ 325 AcpiInstallNotifyHandler(handle, ACPI_ALL_NOTIFY, 326 acpi_cmbat_notify_handler, dev); 327 328 ACPI_SERIAL_BEGIN(cmbat); 329 timespecclear(&sc->bif_lastupdated); 330 timespecclear(&sc->bst_lastupdated); 331 332 if (acpi_cmbat_units == 0) { 333 error = acpi_register_ioctl(ACPIIO_CMBAT_GET_BIF, 334 acpi_cmbat_ioctl, NULL); 335 if (error != 0) { 336 device_printf(dev, "register bif ioctl failed\n"); 337 return (error); 338 } 339 error = acpi_register_ioctl(ACPIIO_CMBAT_GET_BST, 340 acpi_cmbat_ioctl, NULL); 341 if (error != 0) { 342 device_printf(dev, "register bst ioctl failed\n"); 343 return (error); 344 } 345 } 346 347 sc->phys_unit = acpi_cmbat_units; 348 error = acpi_battery_register(ACPI_BATT_TYPE_CMBAT, sc->phys_unit); 349 if (error != 0) { 350 device_printf(dev, "registering battery %d failed\n", sc->phys_unit); 351 return (error); 352 } 353 acpi_cmbat_units++; 354 timespecclear(&acpi_cmbat_info_lastupdated); 355 ACPI_SERIAL_END(cmbat); 356 357 AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_cmbat_init_battery, dev); 358 359 return (0); 360 } 361 362 static int 363 acpi_cmbat_detach(device_t dev) 364 { 365 struct acpi_cmbat_softc *sc; 366 367 sc = device_get_softc(dev); 368 ACPI_SERIAL_BEGIN(cmbat); 369 acpi_battery_remove(ACPI_BATT_TYPE_CMBAT, sc->phys_unit); 370 acpi_cmbat_units--; 371 ACPI_SERIAL_END(cmbat); 372 return (0); 373 } 374 375 static int 376 acpi_cmbat_resume(device_t dev) 377 { 378 AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_cmbat_init_battery, dev); 379 return (0); 380 } 381 382 static int 383 acpi_cmbat_ioctl(u_long cmd, caddr_t addr, void *arg) 384 { 385 device_t dev; 386 union acpi_battery_ioctl_arg *ioctl_arg; 387 struct acpi_cmbat_softc *sc; 388 struct acpi_bif *bifp; 389 struct acpi_bst *bstp; 390 391 ioctl_arg = (union acpi_battery_ioctl_arg *)addr; 392 dev = devclass_get_device(acpi_cmbat_devclass, ioctl_arg->unit); 393 if (dev == NULL) 394 return (ENXIO); 395 sc = device_get_softc(dev); 396 397 /* 398 * No security check required: information retrieval only. If 399 * new functions are added here, a check might be required. 400 */ 401 ACPI_SERIAL_BEGIN(cmbat); 402 switch (cmd) { 403 case ACPIIO_CMBAT_GET_BIF: 404 acpi_cmbat_get_bif(dev); 405 bifp = &ioctl_arg->bif; 406 bifp->units = sc->bif.units; 407 bifp->dcap = sc->bif.dcap; 408 bifp->lfcap = sc->bif.lfcap; 409 bifp->btech = sc->bif.btech; 410 bifp->dvol = sc->bif.dvol; 411 bifp->wcap = sc->bif.wcap; 412 bifp->lcap = sc->bif.lcap; 413 bifp->gra1 = sc->bif.gra1; 414 bifp->gra2 = sc->bif.gra2; 415 strncpy(bifp->model, sc->bif.model, sizeof(sc->bif.model)); 416 strncpy(bifp->serial, sc->bif.serial, sizeof(sc->bif.serial)); 417 strncpy(bifp->type, sc->bif.type, sizeof(sc->bif.type)); 418 strncpy(bifp->oeminfo, sc->bif.oeminfo, sizeof(sc->bif.oeminfo)); 419 break; 420 case ACPIIO_CMBAT_GET_BST: 421 bstp = &ioctl_arg->bst; 422 if (acpi_BatteryIsPresent(dev)) { 423 acpi_cmbat_get_bst(dev); 424 bstp->state = sc->bst.state; 425 bstp->rate = sc->bst.rate; 426 bstp->cap = sc->bst.cap; 427 bstp->volt = sc->bst.volt; 428 } else { 429 bstp->state = ACPI_BATT_STAT_NOT_PRESENT; 430 } 431 break; 432 default: 433 break; 434 } 435 ACPI_SERIAL_END(cmbat); 436 437 return (0); 438 } 439 440 static int 441 acpi_cmbat_is_bst_valid(struct acpi_bst *bst) 442 { 443 if (bst->state >= ACPI_BATT_STAT_MAX || bst->cap == 0xffffffff || 444 bst->volt == 0xffffffff) 445 return (FALSE); 446 else 447 return (TRUE); 448 } 449 450 static int 451 acpi_cmbat_is_bif_valid(struct acpi_bif *bif) 452 { 453 if (bif->lfcap == 0) 454 return (FALSE); 455 else 456 return (TRUE); 457 } 458 459 static int 460 acpi_cmbat_get_total_battinfo(struct acpi_battinfo *battinfo) 461 { 462 int i; 463 int error; 464 int batt_stat; 465 int valid_rate, valid_units; 466 int cap, min; 467 int total_cap, total_min, total_full; 468 struct acpi_cmbat_softc *sc; 469 470 ACPI_SERIAL_ASSERT(cmbat); 471 472 cap = min = -1; 473 batt_stat = ACPI_BATT_STAT_NOT_PRESENT; 474 error = 0; 475 476 /* Get battery status, valid rate and valid units */ 477 batt_stat = valid_rate = valid_units = 0; 478 for (i = 0; i < acpi_cmbat_units; i++) { 479 sc = devclass_get_softc(acpi_cmbat_devclass, i); 480 if (sc == NULL) 481 continue; 482 sc->present = acpi_BatteryIsPresent(sc->dev); 483 if (!sc->present) 484 continue; 485 acpi_cmbat_get_bst(sc->dev); 486 487 /* If battery not installed, we get strange values */ 488 if (!acpi_cmbat_is_bst_valid(&sc->bst) || 489 !acpi_cmbat_is_bif_valid(&sc->bif)) { 490 sc->present = FALSE; 491 continue; 492 } 493 494 valid_units++; 495 sc->cap = 100 * sc->bst.cap / sc->bif.lfcap; 496 497 /* 498 * Some laptops report the "design-capacity" instead of the 499 * "real-capacity" when the battery is fully charged. 500 * That breaks the above arithmetic as it needs to be 100% maximum. 501 */ 502 if (sc->cap > 100) 503 sc->cap = 100; 504 505 batt_stat |= sc->bst.state; 506 507 /* 508 * XXX Hack to calculate total battery time. 509 * 510 * On systems with more than one battery, they may get used 511 * sequentially, thus bst.rate may only signify the one in use. 512 * For the remaining batteries, bst.rate will be zero, which 513 * makes it impossible to calculate the remaining time. Some 514 * other systems may need the sum of all the bst.rate values 515 * when discharging. Therefore, we sum the bst.rate for valid 516 * batteries (ones in the discharging state) and use the sum 517 * to calculate the total remaining time. 518 */ 519 if (sc->bst.rate > 0) { 520 if (sc->bst.state & ACPI_BATT_STAT_DISCHARG) 521 valid_rate += sc->bst.rate; 522 } 523 } 524 525 /* Calculate total battery capacity and time */ 526 total_cap = total_min = total_full = 0; 527 for (i = 0; i < acpi_cmbat_units; i++) { 528 sc = devclass_get_softc(acpi_cmbat_devclass, i); 529 if (!sc->present) 530 continue; 531 532 /* 533 * If any batteries are discharging, use the sum of the bst.rate 534 * values. Otherwise, use the full charge time to estimate 535 * remaining time. If neither are available, assume no charge. 536 */ 537 if (valid_rate > 0) 538 sc->min = 60 * sc->bst.cap / valid_rate; 539 else if (sc->full_charge_time > 0) 540 sc->min = (sc->full_charge_time * sc->cap) / 100; 541 else 542 sc->min = 0; 543 total_min += sc->min; 544 total_cap += sc->cap; 545 total_full += sc->full_charge_time; 546 } 547 548 /* Battery life */ 549 if (valid_units == 0) { 550 cap = -1; 551 batt_stat = ACPI_BATT_STAT_NOT_PRESENT; 552 } else 553 cap = total_cap / valid_units; 554 555 /* Battery time */ 556 if (valid_units == 0) 557 min = -1; 558 else if (valid_rate == 0 || (batt_stat & ACPI_BATT_STAT_CHARGING)) { 559 if (total_full == 0) 560 min = -1; 561 else 562 min = (total_full * cap) / 100; 563 } else 564 min = total_min; 565 acpi_cmbat_info_updated(&acpi_cmbat_info_lastupdated); 566 567 battinfo->cap = cap; 568 battinfo->min = min; 569 battinfo->state = batt_stat; 570 571 return (error); 572 } 573 574 static void 575 acpi_cmbat_init_battery(void *arg) 576 { 577 struct acpi_cmbat_softc *sc; 578 int retry; 579 device_t dev; 580 581 dev = (device_t)arg; 582 sc = device_get_softc(dev); 583 ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), 584 "battery initialization start\n"); 585 586 for (retry = 0; retry < ACPI_CMBAT_RETRY_MAX; retry++, AcpiOsSleep(10, 0)) { 587 sc->present = acpi_BatteryIsPresent(dev); 588 if (!sc->present) 589 continue; 590 591 ACPI_SERIAL_BEGIN(cmbat); 592 timespecclear(&sc->bst_lastupdated); 593 timespecclear(&sc->bif_lastupdated); 594 acpi_cmbat_get_bst(dev); 595 acpi_cmbat_get_bif(dev); 596 ACPI_SERIAL_END(cmbat); 597 598 if (acpi_cmbat_is_bst_valid(&sc->bst) && 599 acpi_cmbat_is_bif_valid(&sc->bif)) 600 break; 601 } 602 603 if (retry == ACPI_CMBAT_RETRY_MAX) { 604 ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), 605 "battery initialization failed, giving up\n"); 606 } else { 607 ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), 608 "battery initialization done, tried %d times\n", retry + 1); 609 } 610 } 611 612 /* 613 * Public interfaces. 614 */ 615 int 616 acpi_cmbat_get_battinfo(int unit, struct acpi_battinfo *battinfo) 617 { 618 int error; 619 struct acpi_cmbat_softc *sc; 620 621 ACPI_SERIAL_BEGIN(cmbat); 622 error = acpi_cmbat_get_total_battinfo(battinfo); 623 if (unit == -1 || error) 624 goto out; 625 626 error = ENXIO; 627 if (unit >= acpi_cmbat_units) 628 goto out; 629 if ((sc = devclass_get_softc(acpi_cmbat_devclass, unit)) == NULL) 630 goto out; 631 632 if (!sc->present) { 633 battinfo->cap = -1; 634 battinfo->min = -1; 635 battinfo->state = ACPI_BATT_STAT_NOT_PRESENT; 636 } else { 637 battinfo->cap = sc->cap; 638 battinfo->min = sc->min; 639 battinfo->state = sc->bst.state; 640 } 641 error = 0; 642 643 out: 644 ACPI_SERIAL_END(cmbat); 645 return (error); 646 } 647