1 /* $NetBSD: wmi_hp.c,v 1.4 2010/10/25 07:53:22 jruoho Exp $ */ 2 3 /*- 4 * Copyright (c) 2009, 2010 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jukka Ruohonen. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 /*- 34 * Copyright (c) 2009 Michael Gmelin <freebsd@grem.de> 35 * All rights reserved. 36 * 37 * Redistribution and use in source and binary forms, with or without 38 * modification, are permitted provided that the following conditions 39 * are met: 40 * 1. Redistributions of source code must retain the above copyright 41 * notice, this list of conditions and the following disclaimer. 42 * 2. Redistributions in binary form must reproduce the above copyright 43 * notice, this list of conditions and the following disclaimer in the 44 * documentation and/or other materials provided with the distribution. 45 * 46 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 47 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 48 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 49 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 50 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 51 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 52 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 53 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 54 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 55 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 56 * SUCH DAMAGE. 57 */ 58 59 #include <sys/cdefs.h> 60 __KERNEL_RCSID(0, "$NetBSD: wmi_hp.c,v 1.4 2010/10/25 07:53:22 jruoho Exp $"); 61 62 #include <sys/param.h> 63 #include <sys/device.h> 64 #include <sys/kmem.h> 65 #include <sys/module.h> 66 67 #include <dev/acpi/acpireg.h> 68 #include <dev/acpi/acpivar.h> 69 #include <dev/acpi/wmi/wmi_acpivar.h> 70 71 #include <dev/sysmon/sysmonvar.h> 72 73 #define _COMPONENT ACPI_RESOURCE_COMPONENT 74 ACPI_MODULE_NAME ("wmi_hp") 75 76 #define WMI_HP_METHOD_ARG_READ 0x01 77 #define WMI_HP_METHOD_ARG_WRITE 0x02 78 #define WMI_HP_METHOD_ARG_WRITE_SIZE 0x04 79 #define WMI_HP_METHOD_ARG_MAGIC 0x55434553 80 #define WMI_HP_METHOD_ARG_SIZE 0x05 * sizeof(uint32_t) 81 82 #define WMI_HP_METHOD_CMD_DISPLAY 0x01 83 #define WMI_HP_METHOD_CMD_HDDTEMP 0x02 84 #define WMI_HP_METHOD_CMD_ALS 0x03 85 #define WMI_HP_METHOD_CMD_DOCK 0x04 86 #define WMI_HP_METHOD_CMD_SWITCH 0x05 87 #define WMI_HP_METHOD_CMD_HOTKEY 0x0C 88 89 #define WMI_HP_EVENT_DOCK 0x01 90 #define WMI_HP_EVENT_HOTKEY 0x04 91 #define WMI_HP_EVENT_SWITCH 0x05 92 /* WMI_HP_EVENT_UNKNOWN 0xXX */ 93 94 #define WMI_HP_HOTKEY_BRIGHTNESS_UP 0x02 95 #define WMI_HP_HOTKEY_BRIGHTNESS_DOWN 0x03 96 /* WMI_HP_HOTKEY_UNKNOWN 0xXX */ 97 98 #define WMI_HP_SWITCH_WLAN 0x01 99 #define WMI_HP_SWITCH_BT 0x02 100 #define WMI_HP_SWITCH_WWAN 0x04 101 102 #define WMI_HP_SWITCH_ARG_WLAN_OFF 0x100 103 #define WMI_HP_SWITCH_ARG_WLAN_ON 0x101 104 #define WMI_HP_SWITCH_ARG_BT_OFF 0x200 105 #define WMI_HP_SWITCH_ARG_BT_ON 0x202 106 #define WMI_HP_SWITCH_ARG_WWAN_OFF 0x400 107 #define WMI_HP_SWITCH_ARG_WWAN_ON 0x404 108 109 #define WMI_HP_SWITCH_MASK_WLAN_ONAIR __BIT(8) 110 #define WMI_HP_SWITCH_MASK_WLAN_ENABLED __BIT(9) 111 #define WMI_HP_SWITCH_MASK_WLAN_RADIO __BIT(11) 112 #define WMI_HP_SWITCH_MASK_BT_ONAIR __BIT(16) 113 #define WMI_HP_SWITCH_MASK_BT_ENABLED __BIT(17) 114 #define WMI_HP_SWITCH_MASK_BT_RADIO __BIT(19) 115 #define WMI_HP_SWITCH_MASK_WWAN_ONAIR __BIT(24) 116 #define WMI_HP_SWITCH_MASK_WWAN_ENABLED __BIT(25) 117 #define WMI_HP_SWITCH_MASK_WWAN_RADIO __BIT(27) 118 119 #define WMI_HP_GUID_EVENT "95F24279-4D7B-4334-9387-ACCDC67EF61C" 120 #define WMI_HP_GUID_METHOD "5FB7F034-2C63-45E9-BE91-3D44E2C707E4" 121 122 #define WMI_HP_SENSOR_WLAN 0 123 #define WMI_HP_SENSOR_BT 1 124 #define WMI_HP_SENSOR_WWAN 2 125 #define WMI_HP_SENSOR_COUNT 3 126 #define WMI_HP_SENSOR_SIZE 3 * sizeof(envsys_data_t) 127 128 struct wmi_hp_softc { 129 device_t sc_dev; 130 device_t sc_parent; 131 struct sysmon_envsys *sc_sme; 132 envsys_data_t *sc_sensor; 133 uint32_t *sc_arg; 134 uint32_t sc_val; 135 }; 136 137 static int wmi_hp_match(device_t, cfdata_t, void *); 138 static void wmi_hp_attach(device_t, device_t, void *); 139 static int wmi_hp_detach(device_t, int); 140 static bool wmi_hp_suspend(device_t, const pmf_qual_t *); 141 static bool wmi_hp_resume(device_t, const pmf_qual_t *); 142 static void wmi_hp_notify_handler(ACPI_HANDLE, uint32_t, void *); 143 static void wmi_hp_hotkey(void *); 144 static bool wmi_hp_method(struct wmi_hp_softc *); 145 static bool wmi_hp_method_read(struct wmi_hp_softc *, uint8_t); 146 147 #if 0 148 static bool wmi_hp_method_write(struct wmi_hp_softc *, uint8_t, uint32_t); 149 #endif 150 151 static void wmi_hp_sensor_init(struct wmi_hp_softc *); 152 static void wmi_hp_sensor_update(void *); 153 154 CFATTACH_DECL_NEW(wmihp, sizeof(struct wmi_hp_softc), 155 wmi_hp_match, wmi_hp_attach, wmi_hp_detach, NULL); 156 157 static int 158 wmi_hp_match(device_t parent, cfdata_t match, void *aux) 159 { 160 return acpi_wmi_guid_match(parent, WMI_HP_GUID_METHOD); 161 } 162 163 static void 164 wmi_hp_attach(device_t parent, device_t self, void *aux) 165 { 166 struct wmi_hp_softc *sc = device_private(self); 167 ACPI_STATUS rv = AE_ERROR; 168 169 sc->sc_dev = self; 170 sc->sc_parent = parent; 171 172 sc->sc_sme = NULL; 173 sc->sc_sensor = NULL; 174 175 sc->sc_arg = kmem_alloc(WMI_HP_METHOD_ARG_SIZE, KM_SLEEP); 176 177 if (sc->sc_arg == NULL) 178 return; 179 180 aprint_naive("\n"); 181 aprint_normal(": HP WMI mappings\n"); 182 183 (void)pmf_device_register(sc->sc_dev, wmi_hp_suspend, wmi_hp_resume); 184 185 if (acpi_wmi_guid_match(parent, WMI_HP_GUID_EVENT) != 0) 186 rv = acpi_wmi_event_register(parent, wmi_hp_notify_handler); 187 188 if (ACPI_FAILURE(rv)) 189 return; 190 191 sc->sc_sensor = kmem_alloc(WMI_HP_SENSOR_SIZE, KM_SLEEP); 192 193 if (sc->sc_sensor == NULL) 194 return; 195 196 wmi_hp_sensor_init(sc); 197 } 198 199 static int 200 wmi_hp_detach(device_t self, int flags) 201 { 202 struct wmi_hp_softc *sc = device_private(self); 203 device_t parent = sc->sc_parent; 204 205 (void)acpi_wmi_event_deregister(parent); 206 207 if (sc->sc_sme != NULL) 208 sysmon_envsys_unregister(sc->sc_sme); 209 210 if (sc->sc_sensor != NULL) 211 kmem_free(sc->sc_sensor, WMI_HP_SENSOR_SIZE); 212 213 if (sc->sc_arg != NULL) 214 kmem_free(sc->sc_arg, WMI_HP_METHOD_ARG_SIZE); 215 216 pmf_device_deregister(self); 217 218 return 0; 219 } 220 221 static bool 222 wmi_hp_suspend(device_t self, const pmf_qual_t *qual) 223 { 224 struct wmi_hp_softc *sc = device_private(self); 225 device_t parent = sc->sc_parent; 226 227 if (sc->sc_sensor != NULL) 228 (void)acpi_wmi_event_deregister(parent); 229 230 return true; 231 } 232 233 static bool 234 wmi_hp_resume(device_t self, const pmf_qual_t *qual) 235 { 236 struct wmi_hp_softc *sc = device_private(self); 237 device_t parent = sc->sc_parent; 238 239 if (sc->sc_sensor != NULL) 240 (void)acpi_wmi_event_register(parent, wmi_hp_notify_handler); 241 242 return true; 243 } 244 245 static void 246 wmi_hp_notify_handler(ACPI_HANDLE hdl, uint32_t evt, void *aux) 247 { 248 static const int handler = OSL_NOTIFY_HANDLER; 249 struct wmi_hp_softc *sc; 250 device_t self = aux; 251 ACPI_OBJECT *obj; 252 ACPI_BUFFER buf; 253 ACPI_STATUS rv; 254 uint32_t val; 255 256 buf.Pointer = NULL; 257 258 sc = device_private(self); 259 rv = acpi_wmi_event_get(sc->sc_parent, evt, &buf); 260 261 if (ACPI_FAILURE(rv)) 262 goto out; 263 264 obj = buf.Pointer; 265 266 if (obj->Type != ACPI_TYPE_BUFFER) { 267 rv = AE_TYPE; 268 goto out; 269 } 270 271 if (obj->Buffer.Length != 8) { 272 rv = AE_LIMIT; 273 goto out; 274 } 275 276 val = *((uint8_t *)obj->Buffer.Pointer); 277 278 if (val == 0x00) { 279 rv = AE_BAD_DATA; 280 goto out; 281 } 282 283 switch (val) { 284 285 case WMI_HP_EVENT_SWITCH: 286 rv = AcpiOsExecute(handler, wmi_hp_sensor_update, self); 287 break; 288 289 case WMI_HP_EVENT_HOTKEY: 290 rv = AcpiOsExecute(handler, wmi_hp_hotkey, self); 291 break; 292 293 case WMI_HP_EVENT_DOCK: /* FALLTHROUGH */ 294 295 default: 296 aprint_debug_dev(sc->sc_dev, "unknown event 0x%02X\n", evt); 297 break; 298 } 299 300 out: 301 if (buf.Pointer != NULL) 302 ACPI_FREE(buf.Pointer); 303 304 if (ACPI_FAILURE(rv)) 305 aprint_error_dev(sc->sc_dev, "failed to get data for " 306 "event 0x%02X: %s\n", evt, AcpiFormatException(rv)); 307 } 308 309 static void 310 wmi_hp_hotkey(void *aux) 311 { 312 struct wmi_hp_softc *sc; 313 device_t self = aux; 314 315 sc = device_private(self); 316 317 if (wmi_hp_method_read(sc, WMI_HP_METHOD_CMD_HOTKEY) != true) 318 return; 319 320 switch (sc->sc_val) { 321 322 case WMI_HP_HOTKEY_BRIGHTNESS_UP: 323 pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_UP); 324 break; 325 326 case WMI_HP_HOTKEY_BRIGHTNESS_DOWN: 327 pmf_event_inject(NULL, PMFE_DISPLAY_BRIGHTNESS_DOWN); 328 break; 329 330 default: 331 aprint_debug_dev(self, "unknown hotkey 0x%02x\n", sc->sc_val); 332 break; 333 } 334 } 335 336 static bool 337 wmi_hp_method(struct wmi_hp_softc *sc) 338 { 339 ACPI_BUFFER ibuf, obuf; 340 ACPI_STATUS rv = AE_OK; 341 ACPI_OBJECT *obj; 342 uint32_t cmd, *val; 343 344 cmd = sc->sc_arg[2]; 345 346 KDASSERT(cmd != 0); 347 KDASSERT(sc->sc_arg[0] == WMI_HP_METHOD_ARG_MAGIC); 348 349 obuf.Pointer = NULL; 350 ibuf.Pointer = sc->sc_arg; 351 ibuf.Length = WMI_HP_METHOD_ARG_SIZE; 352 353 rv = acpi_wmi_method(sc->sc_parent, 354 WMI_HP_GUID_METHOD, 0, 3, &ibuf, &obuf); 355 356 if (ACPI_FAILURE(rv)) 357 goto out; 358 359 obj = obuf.Pointer; 360 361 if (obj->Type != ACPI_TYPE_BUFFER) { 362 rv = AE_TYPE; 363 goto out; 364 } 365 366 /* 367 * val[0] unknown 368 * val[1] error code 369 * val[2] return value 370 */ 371 val = (uint32_t *)obj->Buffer.Pointer; 372 373 if (val[1] != 0) { 374 rv = AE_ERROR; 375 goto out; 376 } 377 378 sc->sc_val = val[2]; 379 380 out: 381 if (obuf.Pointer != NULL) 382 ACPI_FREE(obuf.Pointer); 383 384 if (ACPI_FAILURE(rv)) { 385 aprint_debug_dev(sc->sc_dev, "failed to evaluate method " 386 "(cmd = 0x%02X): %s\n", cmd, AcpiFormatException(rv)); 387 return false; 388 } 389 390 return true; 391 } 392 393 static bool 394 wmi_hp_method_read(struct wmi_hp_softc *sc, uint8_t cmd) 395 { 396 397 sc->sc_arg[0] = WMI_HP_METHOD_ARG_MAGIC; 398 sc->sc_arg[1] = WMI_HP_METHOD_ARG_READ; 399 sc->sc_arg[2] = cmd; 400 sc->sc_arg[3] = 0; 401 sc->sc_arg[4] = 0; 402 403 return wmi_hp_method(sc); 404 } 405 406 #if 0 407 static bool 408 wmi_hp_method_write(struct wmi_hp_softc *sc, uint8_t cmd, uint32_t val) 409 { 410 411 sc->sc_arg[0] = WMI_HP_METHOD_ARG_MAGIC; 412 sc->sc_arg[1] = WMI_HP_METHOD_ARG_WRITE; 413 sc->sc_arg[2] = cmd; 414 sc->sc_arg[3] = WMI_HP_METHOD_ARG_WRITE_SIZE; 415 sc->sc_arg[4] = val; 416 417 return wmi_hp_method(sc); 418 } 419 #endif 420 421 static void 422 wmi_hp_sensor_init(struct wmi_hp_softc *sc) 423 { 424 int i, j, sensor[3]; 425 426 const char desc[][ENVSYS_DESCLEN] = { 427 "wireless", "bluetooth", "mobile" 428 }; 429 430 KDASSERT(sc->sc_sme == NULL); 431 KDASSERT(sc->sc_sensor != NULL); 432 433 (void)memset(sc->sc_sensor, 0, WMI_HP_SENSOR_SIZE); 434 435 if (wmi_hp_method_read(sc, WMI_HP_METHOD_CMD_SWITCH) != true) 436 return; 437 438 sc->sc_sme = sysmon_envsys_create(); 439 440 sensor[0] = WMI_HP_SWITCH_WLAN; 441 sensor[1] = WMI_HP_SWITCH_BT; 442 sensor[2] = WMI_HP_SWITCH_WWAN; 443 444 CTASSERT(WMI_HP_SENSOR_WLAN == 0); 445 CTASSERT(WMI_HP_SENSOR_BT == 1); 446 CTASSERT(WMI_HP_SENSOR_WWAN == 2); 447 448 for (i = j = 0; i < 3; i++) { 449 450 if ((sc->sc_val & sensor[i]) == 0) 451 continue; 452 453 (void)strlcpy(sc->sc_sensor[i].desc, desc[i], ENVSYS_DESCLEN); 454 455 sc->sc_sensor[i].state = ENVSYS_SINVALID; 456 sc->sc_sensor[i].units = ENVSYS_INDICATOR; 457 458 if (sysmon_envsys_sensor_attach(sc->sc_sme, 459 &sc->sc_sensor[i]) != 0) 460 goto fail; 461 462 j++; 463 } 464 465 if (j == 0) 466 goto fail; 467 468 sc->sc_sme->sme_flags = SME_DISABLE_REFRESH; 469 sc->sc_sme->sme_name = device_xname(sc->sc_dev); 470 471 if (sysmon_envsys_register(sc->sc_sme) != 0) 472 goto fail; 473 474 wmi_hp_sensor_update(sc->sc_dev); 475 476 return; 477 478 fail: 479 aprint_debug_dev(sc->sc_dev, "failed to initialize sysmon\n"); 480 481 sysmon_envsys_destroy(sc->sc_sme); 482 kmem_free(sc->sc_sensor, WMI_HP_SENSOR_SIZE); 483 484 sc->sc_sme = NULL; 485 sc->sc_sensor = NULL; 486 } 487 488 static void 489 wmi_hp_sensor_update(void *aux) 490 { 491 struct wmi_hp_softc *sc; 492 device_t self = aux; 493 494 sc = device_private(self); 495 496 if (sc->sc_sme == NULL || sc->sc_sensor == NULL) 497 return; 498 499 if (wmi_hp_method_read(sc, WMI_HP_METHOD_CMD_SWITCH) != true) { 500 sc->sc_sensor[WMI_HP_SENSOR_WLAN].state = ENVSYS_SINVALID; 501 sc->sc_sensor[WMI_HP_SENSOR_WWAN].state = ENVSYS_SINVALID; 502 sc->sc_sensor[WMI_HP_SENSOR_BT].state = ENVSYS_SINVALID; 503 return; 504 } 505 506 if ((sc->sc_val & WMI_HP_SWITCH_WLAN) != 0) { 507 sc->sc_sensor[WMI_HP_SENSOR_WLAN].value_cur = 0; 508 509 if ((sc->sc_val & WMI_HP_SWITCH_MASK_WLAN_ONAIR) != 0) 510 sc->sc_sensor[WMI_HP_SENSOR_WLAN].value_cur = 1; 511 512 sc->sc_sensor[WMI_HP_SENSOR_WLAN].state = ENVSYS_SVALID; 513 } 514 515 if ((sc->sc_val & WMI_HP_SWITCH_BT) != 0) { 516 sc->sc_sensor[WMI_HP_SENSOR_BT].value_cur = 0; 517 518 if ((sc->sc_val & WMI_HP_SWITCH_MASK_BT_ONAIR) != 0) 519 sc->sc_sensor[WMI_HP_SENSOR_BT].value_cur = 1; 520 521 sc->sc_sensor[WMI_HP_SENSOR_BT].state = ENVSYS_SVALID; 522 } 523 524 if ((sc->sc_val & WMI_HP_SWITCH_WWAN) != 0) { 525 sc->sc_sensor[WMI_HP_SENSOR_WWAN].value_cur = 0; 526 527 if ((sc->sc_val & WMI_HP_SWITCH_MASK_WWAN_ONAIR) != 0) 528 sc->sc_sensor[WMI_HP_SENSOR_WWAN].value_cur = 1; 529 530 sc->sc_sensor[WMI_HP_SENSOR_WWAN].state = ENVSYS_SVALID; 531 } 532 } 533 534 #ifdef _MODULE 535 536 MODULE(MODULE_CLASS_DRIVER, wmihp, NULL); 537 CFDRIVER_DECL(wmihp, DV_DULL, NULL); 538 539 static int wmihploc[] = { -1 }; 540 extern struct cfattach wmihp_ca; 541 542 static struct cfparent wmiparent = { 543 "acpiwmibus", NULL, DVUNIT_ANY 544 }; 545 546 static struct cfdata wmihp_cfdata[] = { 547 { 548 .cf_name = "wmihp", 549 .cf_atname = "wmihp", 550 .cf_unit = 0, 551 .cf_fstate = FSTATE_STAR, 552 .cf_loc = wmihploc, 553 .cf_flags = 0, 554 .cf_pspec = &wmiparent, 555 }, 556 557 { NULL, NULL, 0, 0, NULL, 0, NULL } 558 }; 559 560 static int 561 wmihp_modcmd(modcmd_t cmd, void *opaque) 562 { 563 int err; 564 565 switch (cmd) { 566 567 case MODULE_CMD_INIT: 568 569 err = config_cfdriver_attach(&wmihp_cd); 570 571 if (err != 0) 572 return err; 573 574 err = config_cfattach_attach("wmihp", &wmihp_ca); 575 576 if (err != 0) { 577 config_cfdriver_detach(&wmihp_cd); 578 return err; 579 } 580 581 err = config_cfdata_attach(wmihp_cfdata, 1); 582 583 if (err != 0) { 584 config_cfattach_detach("wmihp", &wmihp_ca); 585 config_cfdriver_detach(&wmihp_cd); 586 return err; 587 } 588 589 return 0; 590 591 case MODULE_CMD_FINI: 592 593 err = config_cfdata_detach(wmihp_cfdata); 594 595 if (err != 0) 596 return err; 597 598 config_cfattach_detach("wmihp", &wmihp_ca); 599 config_cfdriver_detach(&wmihp_cd); 600 601 return 0; 602 603 default: 604 return ENOTTY; 605 } 606 } 607 608 #endif /* _MODULE */ 609