1 /* $NetBSD: aibs_acpi.c,v 1.1 2011/06/12 07:25:43 jruoho Exp $ */ 2 3 /*- 4 * Copyright (c) 2011 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 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* $OpenBSD: atk0110.c,v 1.1 2009/07/23 01:38:16 cnst Exp $ */ 33 /* 34 * Copyright (c) 2009 Constantine A. Murenin <cnst+netbsd@bugmail.mojo.ru> 35 * 36 * Permission to use, copy, modify, and distribute this software for any 37 * purpose with or without fee is hereby granted, provided that the above 38 * copyright notice and this permission notice appear in all copies. 39 * 40 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 41 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 42 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 43 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 44 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 45 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 46 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 47 */ 48 49 #include <sys/cdefs.h> 50 __KERNEL_RCSID(0, "$NetBSD: aibs_acpi.c,v 1.1 2011/06/12 07:25:43 jruoho Exp $"); 51 52 #include <sys/param.h> 53 #include <sys/kmem.h> 54 #include <sys/module.h> 55 56 #include <dev/acpi/acpireg.h> 57 #include <dev/acpi/acpivar.h> 58 59 /* 60 * ASUSTeK AI Booster (ACPI ASOC ATK0110). 61 * 62 * This code was originally written for OpenBSD after the techniques 63 * described in the Linux's asus_atk0110.c and FreeBSD's acpi_aiboost.c 64 * were verified to be accurate on the actual hardware kindly provided by 65 * Sam Fourman Jr. It was subsequently ported from OpenBSD to DragonFly BSD, 66 * and then to the NetBSD's sysmon_envsys(9) framework. 67 * 68 * -- Constantine A. Murenin <http://cnst.su/> 69 */ 70 71 #define _COMPONENT ACPI_RESOURCE_COMPONENT 72 ACPI_MODULE_NAME ("acpi_aibs") 73 74 #define AIBS_MUX_HWMON 0x00000006 75 #define AIBS_MUX_MGMT 0x00000011 76 77 #define AIBS_TYPE(x) (((x) >> 16) & 0xff) 78 #define AIBS_TYPE_VOLT 2 79 #define AIBS_TYPE_TEMP 3 80 #define AIBS_TYPE_FAN 4 81 82 struct aibs_sensor { 83 envsys_data_t as_sensor; 84 uint64_t as_type; 85 uint64_t as_liml; 86 uint64_t as_limh; 87 88 SIMPLEQ_ENTRY(aibs_sensor) as_list; 89 }; 90 91 struct aibs_softc { 92 device_t sc_dev; 93 struct acpi_devnode *sc_node; 94 struct sysmon_envsys *sc_sme; 95 bool sc_model; /* new model = true */ 96 97 SIMPLEQ_HEAD(, aibs_sensor) as_head; 98 }; 99 100 static int aibs_match(device_t, cfdata_t, void *); 101 static void aibs_attach(device_t, device_t, void *); 102 static int aibs_detach(device_t, int); 103 104 static void aibs_init(device_t); 105 static void aibs_init_new(device_t); 106 static void aibs_init_old(device_t, int); 107 108 static void aibs_sensor_add(device_t, ACPI_OBJECT *); 109 static bool aibs_sensor_value(device_t, struct aibs_sensor *, uint64_t *); 110 static void aibs_sensor_refresh(struct sysmon_envsys *, envsys_data_t *); 111 static void aibs_sensor_limits(struct sysmon_envsys *, envsys_data_t *, 112 sysmon_envsys_lim_t *, uint32_t *); 113 114 CFATTACH_DECL_NEW(aibs, sizeof(struct aibs_softc), 115 aibs_match, aibs_attach, aibs_detach, NULL); 116 117 static const char* const aibs_hid[] = { 118 "ATK0110", 119 NULL 120 }; 121 122 static int 123 aibs_match(device_t parent, cfdata_t match, void *aux) 124 { 125 struct acpi_attach_args *aa = aux; 126 127 if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE) 128 return 0; 129 130 return acpi_match_hid(aa->aa_node->ad_devinfo, aibs_hid); 131 } 132 133 static void 134 aibs_attach(device_t parent, device_t self, void *aux) 135 { 136 struct aibs_softc *sc = device_private(self); 137 struct acpi_attach_args *aa = aux; 138 139 sc->sc_dev = self; 140 sc->sc_node = aa->aa_node; 141 142 aprint_naive("\n"); 143 aprint_normal(": ASUSTeK AI Booster\n"); 144 145 sc->sc_sme = sysmon_envsys_create(); 146 147 sc->sc_sme->sme_cookie = sc; 148 sc->sc_sme->sme_name = device_xname(self); 149 sc->sc_sme->sme_refresh = aibs_sensor_refresh; 150 sc->sc_sme->sme_get_limits = aibs_sensor_limits; 151 152 aibs_init(self); 153 SIMPLEQ_INIT(&sc->as_head); 154 155 if (sc->sc_model != false) 156 aibs_init_new(self); 157 else { 158 aibs_init_old(self, AIBS_TYPE_FAN); 159 aibs_init_old(self, AIBS_TYPE_TEMP); 160 aibs_init_old(self, AIBS_TYPE_VOLT); 161 } 162 163 (void)pmf_device_register(self, NULL, NULL); 164 165 if (sc->sc_sme->sme_nsensors == 0) { 166 aprint_error_dev(self, "no sensors found\n"); 167 sysmon_envsys_destroy(sc->sc_sme); 168 sc->sc_sme = NULL; 169 return; 170 } 171 172 if (sysmon_envsys_register(sc->sc_sme) != 0) 173 aprint_error_dev(self, "failed to register with sysmon\n"); 174 } 175 176 static int 177 aibs_detach(device_t self, int flags) 178 { 179 struct aibs_softc *sc = device_private(self); 180 struct aibs_sensor *as; 181 182 pmf_device_deregister(self); 183 184 if (sc->sc_sme != NULL) 185 sysmon_envsys_unregister(sc->sc_sme); 186 187 while (SIMPLEQ_FIRST(&sc->as_head) != NULL) { 188 as = SIMPLEQ_FIRST(&sc->as_head); 189 SIMPLEQ_REMOVE_HEAD(&sc->as_head, as_list); 190 kmem_free(as, sizeof(*as)); 191 } 192 193 return 0; 194 } 195 196 static void 197 aibs_init(device_t self) 198 { 199 struct aibs_softc *sc = device_private(self); 200 ACPI_HANDLE tmp; 201 ACPI_STATUS rv; 202 203 /* 204 * Old model uses the tuple { TSIF, VSIF, FSIF } to 205 * enumerate the sensors and { RTMP, RVLT, RFAN } 206 * to obtain the values. New mode uses GGRP for the 207 * enumeration and { GITM, SITM } as accessors. 208 */ 209 rv = AcpiGetHandle(sc->sc_node->ad_handle, "GGRP", &tmp); 210 211 if (ACPI_FAILURE(rv)) { 212 sc->sc_model = false; 213 return; 214 } 215 216 rv = AcpiGetHandle(sc->sc_node->ad_handle, "GITM", &tmp); 217 218 if (ACPI_FAILURE(rv)) { 219 sc->sc_model = false; 220 return; 221 } 222 223 rv = AcpiGetHandle(sc->sc_node->ad_handle, "SITM", &tmp); 224 225 if (ACPI_FAILURE(rv)) { 226 sc->sc_model = false; 227 return; 228 } 229 230 sc->sc_model = true; 231 } 232 233 static void 234 aibs_init_new(device_t self) 235 { 236 struct aibs_softc *sc = device_private(self); 237 ACPI_OBJECT_LIST arg; 238 ACPI_OBJECT id, *obj; 239 ACPI_BUFFER buf; 240 ACPI_STATUS rv; 241 uint32_t i, n; 242 243 arg.Count = 1; 244 arg.Pointer = &id; 245 246 id.Type = ACPI_TYPE_INTEGER; 247 id.Integer.Value = AIBS_MUX_HWMON; 248 249 buf.Pointer = NULL; 250 buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER; 251 252 rv = AcpiEvaluateObject(sc->sc_node->ad_handle, "GGRP", &arg, &buf); 253 254 if (ACPI_FAILURE(rv)) 255 goto out; 256 257 obj = buf.Pointer; 258 259 if (obj->Type != ACPI_TYPE_PACKAGE) { 260 rv = AE_TYPE; 261 goto out; 262 } 263 264 if (obj->Package.Count > UINT32_MAX) { 265 rv = AE_AML_NUMERIC_OVERFLOW; 266 goto out; 267 } 268 269 n = obj->Package.Count; 270 271 if (n == 0) { 272 rv = AE_NOT_EXIST; 273 goto out; 274 } 275 276 for (i = 0; i < n; i++) 277 aibs_sensor_add(self, &obj->Package.Elements[i]); 278 279 out: 280 if (buf.Pointer != NULL) 281 ACPI_FREE(buf.Pointer); 282 283 if (ACPI_FAILURE(rv)) { 284 285 aprint_error_dev(self, "failed to evaluate " 286 "GGRP: %s\n", AcpiFormatException(rv)); 287 } 288 } 289 290 static void 291 aibs_init_old(device_t self, int type) 292 { 293 struct aibs_softc *sc = device_private(self); 294 char path[] = "?SIF"; 295 ACPI_OBJECT *elm, *obj; 296 ACPI_BUFFER buf; 297 ACPI_STATUS rv; 298 uint32_t i, n; 299 300 switch (type) { 301 302 case AIBS_TYPE_FAN: 303 path[0] = 'F'; 304 break; 305 306 case AIBS_TYPE_TEMP: 307 path[0] = 'T'; 308 break; 309 310 case AIBS_TYPE_VOLT: 311 path[0] = 'V'; 312 break; 313 314 default: 315 return; 316 } 317 318 rv = acpi_eval_struct(sc->sc_node->ad_handle, path, &buf); 319 320 if (ACPI_FAILURE(rv)) 321 goto out; 322 323 obj = buf.Pointer; 324 325 if (obj->Type != ACPI_TYPE_PACKAGE) { 326 rv = AE_TYPE; 327 goto out; 328 } 329 330 elm = obj->Package.Elements; 331 332 if (elm[0].Type != ACPI_TYPE_INTEGER) { 333 rv = AE_TYPE; 334 goto out; 335 } 336 337 if (elm[0].Integer.Value > UINT32_MAX) { 338 rv = AE_AML_NUMERIC_OVERFLOW; 339 goto out; 340 } 341 342 n = elm[0].Integer.Value; 343 344 if (n == 0) { 345 rv = AE_NOT_EXIST; 346 goto out; 347 } 348 349 if (obj->Package.Count - 1 != n) { 350 rv = AE_BAD_VALUE; 351 goto out; 352 } 353 354 for (i = 1; i < obj->Package.Count; i++) { 355 356 if (elm[i].Type != ACPI_TYPE_PACKAGE) 357 continue; 358 359 aibs_sensor_add(self, &elm[i]); 360 } 361 362 out: 363 if (buf.Pointer != NULL) 364 ACPI_FREE(buf.Pointer); 365 366 if (ACPI_FAILURE(rv)) { 367 368 aprint_error_dev(self, "failed to evaluate " 369 "%s: %s\n", path, AcpiFormatException(rv)); 370 } 371 } 372 373 static void 374 aibs_sensor_add(device_t self, ACPI_OBJECT *obj) 375 { 376 struct aibs_softc *sc = device_private(self); 377 struct aibs_sensor *as; 378 int ena, len, lhi, llo; 379 const char *name; 380 ACPI_STATUS rv; 381 382 as = NULL; 383 rv = AE_OK; 384 385 if (obj->Type != ACPI_TYPE_PACKAGE) { 386 rv = AE_TYPE; 387 goto out; 388 } 389 390 /* 391 * The known formats are: 392 * 393 * index type old new 394 * ----- ---- --- --- 395 * 0 integer flags flags 396 * 1 string name name 397 * 2 integer limit1 unknown 398 * 3 integer limit2 unknown 399 * 4 integer enable limit1 400 * 5 integer - limit2 401 * 6 integer - enable 402 */ 403 if (sc->sc_model != false) { 404 len = 7; 405 llo = 4; 406 lhi = 5; 407 ena = 6; 408 } else { 409 len = 5; 410 llo = 2; 411 lhi = 3; 412 ena = 4; 413 } 414 415 if (obj->Package.Count != (uint32_t)len) { 416 rv = AE_LIMIT; 417 goto out; 418 } 419 420 if (obj->Package.Elements[0].Type != ACPI_TYPE_INTEGER || 421 obj->Package.Elements[1].Type != ACPI_TYPE_STRING || 422 obj->Package.Elements[llo].Type != ACPI_TYPE_INTEGER || 423 obj->Package.Elements[lhi].Type != ACPI_TYPE_INTEGER || 424 obj->Package.Elements[ena].Type != ACPI_TYPE_INTEGER) { 425 rv = AE_TYPE; 426 goto out; 427 } 428 429 as = kmem_zalloc(sizeof(*as), KM_SLEEP); 430 431 if (as == NULL) { 432 rv = AE_NO_MEMORY; 433 goto out; 434 } 435 436 name = obj->Package.Elements[1].String.Pointer; 437 438 as->as_type = obj->Package.Elements[0].Integer.Value; 439 as->as_liml = obj->Package.Elements[llo].Integer.Value; 440 as->as_limh = obj->Package.Elements[lhi].Integer.Value; 441 442 if (sc->sc_model != false) 443 as->as_limh += as->as_liml; /* A range in the new model. */ 444 445 switch (AIBS_TYPE(as->as_type)) { 446 447 case AIBS_TYPE_FAN: 448 as->as_sensor.units = ENVSYS_SFANRPM; 449 as->as_sensor.flags |= ENVSYS_FMONLIMITS; 450 break; 451 452 case AIBS_TYPE_TEMP: 453 as->as_sensor.units = ENVSYS_STEMP; 454 as->as_sensor.flags |= ENVSYS_FMONLIMITS; 455 break; 456 457 case AIBS_TYPE_VOLT: 458 as->as_sensor.units = ENVSYS_SVOLTS_DC; 459 as->as_sensor.flags |= ENVSYS_FMONLIMITS; 460 break; 461 462 default: 463 rv = AE_TYPE; 464 goto out; 465 } 466 467 (void)strlcpy(as->as_sensor.desc, name, sizeof(as->as_sensor.desc)); 468 469 if (sysmon_envsys_sensor_attach(sc->sc_sme, &as->as_sensor) != 0) { 470 rv = AE_AML_INTERNAL; 471 goto out; 472 } 473 474 SIMPLEQ_INSERT_TAIL(&sc->as_head, as, as_list); 475 476 out: 477 if (ACPI_FAILURE(rv)) { 478 479 if (as != NULL) 480 kmem_free(as, sizeof(*as)); 481 482 aprint_error_dev(self, "failed to add " 483 "sensor: %s\n", AcpiFormatException(rv)); 484 } 485 } 486 487 static bool 488 aibs_sensor_value(device_t self, struct aibs_sensor *as, uint64_t *val) 489 { 490 struct aibs_softc *sc = device_private(self); 491 uint32_t type, *ret, cmb[3]; 492 ACPI_OBJECT_LIST arg; 493 ACPI_OBJECT cmi, tmp; 494 ACPI_OBJECT *obj; 495 ACPI_BUFFER buf; 496 ACPI_STATUS rv; 497 const char *path; 498 499 if (sc->sc_model != false) { 500 501 path = "GITM"; 502 503 cmb[0] = as->as_type; 504 cmb[1] = 0; 505 cmb[2] = 0; 506 507 arg.Count = 1; 508 arg.Pointer = &tmp; 509 510 tmp.Buffer.Length = sizeof(cmb); 511 tmp.Buffer.Pointer = (uint8_t *)cmb; 512 tmp.Type = type = ACPI_TYPE_BUFFER; 513 514 } else { 515 516 arg.Count = 1; 517 arg.Pointer = &cmi; 518 519 cmi.Integer.Value = as->as_type; 520 cmi.Type = type = ACPI_TYPE_INTEGER; 521 522 switch (AIBS_TYPE(as->as_type)) { 523 524 case AIBS_TYPE_FAN: 525 path = "RFAN"; 526 break; 527 528 case AIBS_TYPE_TEMP: 529 path = "RTMP"; 530 break; 531 532 case AIBS_TYPE_VOLT: 533 path = "RVLT"; 534 break; 535 536 default: 537 return false; 538 } 539 } 540 541 buf.Pointer = NULL; 542 buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER; 543 544 rv = AcpiEvaluateObject(sc->sc_node->ad_handle, path, &arg, &buf); 545 546 if (ACPI_FAILURE(rv)) 547 goto out; 548 549 obj = buf.Pointer; 550 551 if (obj->Type != type) { 552 rv = AE_TYPE; 553 goto out; 554 } 555 556 if (sc->sc_model != true) 557 *val = obj->Integer.Value; 558 else { 559 /* 560 * The return buffer contains at least: 561 * 562 * uint32_t buf[0] flags 563 * uint32_t buf[1] return value 564 * uint8_t buf[2-] unknown 565 */ 566 if (obj->Buffer.Length < 8) { 567 rv = AE_BUFFER_OVERFLOW; 568 goto out; 569 } 570 571 ret = (uint32_t *)obj->Buffer.Pointer; 572 573 if (ret[0] == 0) { 574 rv = AE_BAD_VALUE; 575 goto out; 576 } 577 578 *val = ret[1]; 579 } 580 581 out: 582 if (buf.Pointer != NULL) 583 ACPI_FREE(buf.Pointer); 584 585 if (ACPI_FAILURE(rv)) { 586 587 aprint_error_dev(self, "failed to evaluate " 588 "%s: %s\n", path, AcpiFormatException(rv)); 589 590 return false; 591 } 592 593 return true; 594 } 595 596 static void 597 aibs_sensor_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 598 { 599 struct aibs_softc *sc = sme->sme_cookie; 600 struct aibs_sensor *tmp, *as = NULL; 601 envsys_data_t *s = edata; 602 uint64_t val = 0; 603 604 SIMPLEQ_FOREACH(tmp, &sc->as_head, as_list) { 605 606 if (tmp->as_sensor.sensor == s->sensor) { 607 as = tmp; 608 break; 609 } 610 } 611 612 if (as == NULL) { 613 aprint_debug_dev(sc->sc_dev, "failed to find sensor\n"); 614 return; 615 } 616 617 as->as_sensor.state = ENVSYS_SINVALID; 618 as->as_sensor.flags |= ENVSYS_FMONNOTSUPP; 619 620 if (aibs_sensor_value(sc->sc_dev, as, &val) != true) 621 return; 622 623 switch (as->as_sensor.units) { 624 625 case ENVSYS_SFANRPM: 626 as->as_sensor.value_cur = val; 627 break; 628 629 case ENVSYS_STEMP: 630 631 if (val == 0) 632 return; 633 634 as->as_sensor.value_cur = val * 100 * 1000 + 273150000; 635 break; 636 637 case ENVSYS_SVOLTS_DC: 638 as->as_sensor.value_cur = val * 1000; 639 break; 640 641 default: 642 return; 643 } 644 645 as->as_sensor.state = ENVSYS_SVALID; 646 as->as_sensor.flags &= ~ENVSYS_FMONNOTSUPP; 647 } 648 649 static void 650 aibs_sensor_limits(struct sysmon_envsys *sme, envsys_data_t *edata, 651 sysmon_envsys_lim_t *limits, uint32_t *props) 652 { 653 struct aibs_softc *sc = sme->sme_cookie; 654 struct aibs_sensor *tmp, *as = NULL; 655 sysmon_envsys_lim_t *lim = limits; 656 envsys_data_t *s = edata; 657 658 SIMPLEQ_FOREACH(tmp, &sc->as_head, as_list) { 659 660 if (tmp->as_sensor.sensor == s->sensor) { 661 as = tmp; 662 break; 663 } 664 } 665 666 if (as == NULL) { 667 aprint_debug_dev(sc->sc_dev, "failed to find sensor\n"); 668 return; 669 } 670 671 switch (as->as_sensor.units) { 672 673 case ENVSYS_SFANRPM: 674 675 /* 676 * Some boards have strange limits for fans. 677 */ 678 if (as->as_liml == 0) { 679 lim->sel_warnmin = as->as_limh; 680 *props = PROP_WARNMIN; 681 682 } else { 683 lim->sel_warnmin = as->as_liml; 684 lim->sel_warnmax = as->as_limh; 685 *props = PROP_WARNMIN | PROP_WARNMAX; 686 } 687 688 break; 689 690 case ENVSYS_STEMP: 691 lim->sel_critmax = as->as_limh * 100 * 1000 + 273150000; 692 lim->sel_warnmax = as->as_liml * 100 * 1000 + 273150000; 693 694 *props = PROP_CRITMAX | PROP_WARNMAX; 695 break; 696 697 case ENVSYS_SVOLTS_DC: 698 lim->sel_critmin = as->as_liml * 1000; 699 lim->sel_critmax = as->as_limh * 1000; 700 *props = PROP_CRITMIN | PROP_CRITMAX; 701 break; 702 703 default: 704 return; 705 } 706 } 707 708 MODULE(MODULE_CLASS_DRIVER, aibs, NULL); 709 710 #ifdef _MODULE 711 #include "ioconf.c" 712 #endif 713 714 static int 715 aibs_modcmd(modcmd_t cmd, void *aux) 716 { 717 int rv = 0; 718 719 switch (cmd) { 720 721 case MODULE_CMD_INIT: 722 723 #ifdef _MODULE 724 rv = config_init_component(cfdriver_ioconf_aibs, 725 cfattach_ioconf_aibs, cfdata_ioconf_aibs); 726 #endif 727 break; 728 729 case MODULE_CMD_FINI: 730 731 #ifdef _MODULE 732 rv = config_fini_component(cfdriver_ioconf_aibs, 733 cfattach_ioconf_aibs, cfdata_ioconf_aibs); 734 #endif 735 break; 736 737 default: 738 rv = ENOTTY; 739 } 740 741 return rv; 742 } 743