aibs_acpi.c revision 1.2 1 /* $NetBSD: aibs_acpi.c,v 1.2 2011/06/20 17:21:50 pgoyette 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 (at) 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.2 2011/06/20 17:21:50 pgoyette 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 as->as_sensor.state = ENVSYS_SINVALID;
469
470 if (sysmon_envsys_sensor_attach(sc->sc_sme, &as->as_sensor) != 0) {
471 rv = AE_AML_INTERNAL;
472 goto out;
473 }
474
475 SIMPLEQ_INSERT_TAIL(&sc->as_head, as, as_list);
476
477 out:
478 if (ACPI_FAILURE(rv)) {
479
480 if (as != NULL)
481 kmem_free(as, sizeof(*as));
482
483 aprint_error_dev(self, "failed to add "
484 "sensor: %s\n", AcpiFormatException(rv));
485 }
486 }
487
488 static bool
489 aibs_sensor_value(device_t self, struct aibs_sensor *as, uint64_t *val)
490 {
491 struct aibs_softc *sc = device_private(self);
492 uint32_t type, *ret, cmb[3];
493 ACPI_OBJECT_LIST arg;
494 ACPI_OBJECT cmi, tmp;
495 ACPI_OBJECT *obj;
496 ACPI_BUFFER buf;
497 ACPI_STATUS rv;
498 const char *path;
499
500 if (sc->sc_model != false) {
501
502 path = "GITM";
503
504 cmb[0] = as->as_type;
505 cmb[1] = 0;
506 cmb[2] = 0;
507
508 arg.Count = 1;
509 arg.Pointer = &tmp;
510
511 tmp.Buffer.Length = sizeof(cmb);
512 tmp.Buffer.Pointer = (uint8_t *)cmb;
513 tmp.Type = type = ACPI_TYPE_BUFFER;
514
515 } else {
516
517 arg.Count = 1;
518 arg.Pointer = &cmi;
519
520 cmi.Integer.Value = as->as_type;
521 cmi.Type = type = ACPI_TYPE_INTEGER;
522
523 switch (AIBS_TYPE(as->as_type)) {
524
525 case AIBS_TYPE_FAN:
526 path = "RFAN";
527 break;
528
529 case AIBS_TYPE_TEMP:
530 path = "RTMP";
531 break;
532
533 case AIBS_TYPE_VOLT:
534 path = "RVLT";
535 break;
536
537 default:
538 return false;
539 }
540 }
541
542 buf.Pointer = NULL;
543 buf.Length = ACPI_ALLOCATE_LOCAL_BUFFER;
544
545 rv = AcpiEvaluateObject(sc->sc_node->ad_handle, path, &arg, &buf);
546
547 if (ACPI_FAILURE(rv))
548 goto out;
549
550 obj = buf.Pointer;
551
552 if (obj->Type != type) {
553 rv = AE_TYPE;
554 goto out;
555 }
556
557 if (sc->sc_model != true)
558 *val = obj->Integer.Value;
559 else {
560 /*
561 * The return buffer contains at least:
562 *
563 * uint32_t buf[0] flags
564 * uint32_t buf[1] return value
565 * uint8_t buf[2-] unknown
566 */
567 if (obj->Buffer.Length < 8) {
568 rv = AE_BUFFER_OVERFLOW;
569 goto out;
570 }
571
572 ret = (uint32_t *)obj->Buffer.Pointer;
573
574 if (ret[0] == 0) {
575 rv = AE_BAD_VALUE;
576 goto out;
577 }
578
579 *val = ret[1];
580 }
581
582 out:
583 if (buf.Pointer != NULL)
584 ACPI_FREE(buf.Pointer);
585
586 if (ACPI_FAILURE(rv)) {
587
588 aprint_error_dev(self, "failed to evaluate "
589 "%s: %s\n", path, AcpiFormatException(rv));
590
591 return false;
592 }
593
594 return true;
595 }
596
597 static void
598 aibs_sensor_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
599 {
600 struct aibs_softc *sc = sme->sme_cookie;
601 struct aibs_sensor *tmp, *as = NULL;
602 envsys_data_t *s = edata;
603 uint64_t val = 0;
604
605 SIMPLEQ_FOREACH(tmp, &sc->as_head, as_list) {
606
607 if (tmp->as_sensor.sensor == s->sensor) {
608 as = tmp;
609 break;
610 }
611 }
612
613 if (as == NULL) {
614 aprint_debug_dev(sc->sc_dev, "failed to find sensor\n");
615 return;
616 }
617
618 as->as_sensor.state = ENVSYS_SINVALID;
619 as->as_sensor.flags |= ENVSYS_FMONNOTSUPP;
620
621 if (aibs_sensor_value(sc->sc_dev, as, &val) != true)
622 return;
623
624 switch (as->as_sensor.units) {
625
626 case ENVSYS_SFANRPM:
627 as->as_sensor.value_cur = val;
628 break;
629
630 case ENVSYS_STEMP:
631
632 if (val == 0)
633 return;
634
635 as->as_sensor.value_cur = val * 100 * 1000 + 273150000;
636 break;
637
638 case ENVSYS_SVOLTS_DC:
639 as->as_sensor.value_cur = val * 1000;
640 break;
641
642 default:
643 return;
644 }
645
646 as->as_sensor.state = ENVSYS_SVALID;
647 as->as_sensor.flags &= ~ENVSYS_FMONNOTSUPP;
648 }
649
650 static void
651 aibs_sensor_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
652 sysmon_envsys_lim_t *limits, uint32_t *props)
653 {
654 struct aibs_softc *sc = sme->sme_cookie;
655 struct aibs_sensor *tmp, *as = NULL;
656 sysmon_envsys_lim_t *lim = limits;
657 envsys_data_t *s = edata;
658
659 SIMPLEQ_FOREACH(tmp, &sc->as_head, as_list) {
660
661 if (tmp->as_sensor.sensor == s->sensor) {
662 as = tmp;
663 break;
664 }
665 }
666
667 if (as == NULL) {
668 aprint_debug_dev(sc->sc_dev, "failed to find sensor\n");
669 return;
670 }
671
672 switch (as->as_sensor.units) {
673
674 case ENVSYS_SFANRPM:
675
676 /*
677 * Some boards have strange limits for fans.
678 */
679 if (as->as_liml == 0) {
680 lim->sel_warnmin = as->as_limh;
681 *props = PROP_WARNMIN;
682
683 } else {
684 lim->sel_warnmin = as->as_liml;
685 lim->sel_warnmax = as->as_limh;
686 *props = PROP_WARNMIN | PROP_WARNMAX;
687 }
688
689 break;
690
691 case ENVSYS_STEMP:
692 lim->sel_critmax = as->as_limh * 100 * 1000 + 273150000;
693 lim->sel_warnmax = as->as_liml * 100 * 1000 + 273150000;
694
695 *props = PROP_CRITMAX | PROP_WARNMAX;
696 break;
697
698 case ENVSYS_SVOLTS_DC:
699 lim->sel_critmin = as->as_liml * 1000;
700 lim->sel_critmax = as->as_limh * 1000;
701 *props = PROP_CRITMIN | PROP_CRITMAX;
702 break;
703
704 default:
705 return;
706 }
707 }
708
709 MODULE(MODULE_CLASS_DRIVER, aibs, NULL);
710
711 #ifdef _MODULE
712 #include "ioconf.c"
713 #endif
714
715 static int
716 aibs_modcmd(modcmd_t cmd, void *aux)
717 {
718 int rv = 0;
719
720 switch (cmd) {
721
722 case MODULE_CMD_INIT:
723
724 #ifdef _MODULE
725 rv = config_init_component(cfdriver_ioconf_aibs,
726 cfattach_ioconf_aibs, cfdata_ioconf_aibs);
727 #endif
728 break;
729
730 case MODULE_CMD_FINI:
731
732 #ifdef _MODULE
733 rv = config_fini_component(cfdriver_ioconf_aibs,
734 cfattach_ioconf_aibs, cfdata_ioconf_aibs);
735 #endif
736 break;
737
738 default:
739 rv = ENOTTY;
740 }
741
742 return rv;
743 }
744