xref: /illumos-gate/usr/src/uts/i86pc/io/acpi_drv/acpi_video.c (revision 27954b0d964ffcb749cf19296906e7fecdf3da1b)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  *  Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 /*
26  * Solaris x86 Generic ACPI Video Extensions Hotkey driver
27  */
28 #include <sys/hotkey_drv.h>
29 #include <sys/smbios.h>
30 
31 /*
32  * Vendor specific hotkey support list
33  * 	1. Toshiba: acpi_toshiba
34  */
35 struct vendor_hotkey_drv vendor_hotkey_drv_list[] = {
36 /* vendor,	module name,		enable? */
37 {"Toshiba",	"acpi_toshiba",		B_TRUE},
38 /* Terminator */
39 {NULL,		NULL,			B_FALSE}
40 };
41 
42 enum vga_output_type {
43 	OUTPUT_OTHER,
44 	OUTPUT_CRT,
45 	OUTPUT_TV,
46 	OUTPUT_DVI,
47 	OUTPUT_LCD
48 };
49 
50 struct acpi_video_output {
51 	struct acpi_drv_dev dev;
52 	uint32_t			adr;
53 	enum vga_output_type		type;
54 	struct acpi_video_output	*next;
55 };
56 
57 struct acpi_video_brightness {
58 	struct acpi_drv_dev dev;
59 	uint32_t			adr;
60 	uint32_t			nlevel;
61 	int				*levels;
62 	int				cur_level;
63 	uint32_t			cur_level_index;
64 	uint32_t			output_index;
65 	struct acpi_video_brightness	*next;
66 };
67 
68 struct acpi_video_switch {
69 	struct acpi_drv_dev		dev;
70 	struct acpi_video_switch	*next;
71 };
72 
73 /* ACPI video extension hotkey for video switch and brightness control */
74 static struct acpi_video {
75 	struct acpi_video_output	*vid_outputs;
76 	uint32_t			total_outputs;
77 	struct acpi_video_brightness	*vid_brightness;
78 	uint32_t			total_brightness;
79 	struct acpi_video_switch	*vid_switch;
80 	uint32_t			total_switch;
81 } acpi_video_hotkey;
82 
83 int hotkey_drv_debug = 0;
84 
85 static struct acpi_video_smbios_info {
86 	char *manufacturer;
87 	char *product;
88 } acpi_brightness_get_blacklist[] = {
89 	{ /* Dell AdamoXPS laptop */
90 		"Dell Inc.",
91 		"Adamo XPS"
92 	},
93 	{ /* termination entry */
94 		NULL,
95 		NULL
96 	}
97 };
98 /*
99  * -1 = check acpi_brightness_get_blacklist[].
100  * 0 = enable brightness get.
101  * 1 = disable brightness get.
102  */
103 int acpi_brightness_get_disable = -1;
104 
105 
106 #define	ACPI_METHOD_DOS			"_DOS"
107 #define	ACPI_METHOD_DOD			"_DOD"
108 
109 #define	ACPI_DEVNAME_CRT		"CRT"
110 #define	ACPI_DEVNAME_LCD		"LCD"
111 #define	ACPI_DEVNAME_TV			"TV"
112 #define	ACPI_METHOD_ADR			"_ADR"
113 #define	ACPI_METHOD_DDC			"_DDC"
114 #define	ACPI_METHOD_DCS			"_DCS"
115 #define	ACPI_METHOD_DGS			"_DGS"
116 #define	ACPI_METHOD_DSS			"_DSS"
117 
118 #define	VIDEO_NOTIFY_SWITCH		0x80
119 #define	VIDEO_NOTIFY_SWITCH_STATUS	0x81
120 #define	VIDEO_NOTIFY_SWITCH_CYCLE	0x82
121 #define	VIDEO_NOTIFY_SWITCH_NEXT	0x83
122 #define	VIDEO_NOTIFY_SWITCH_PREV	0x84
123 
124 #define	VIDEO_NOTIFY_BRIGHTNESS_CYCLE	0x85
125 #define	VIDEO_NOTIFY_BRIGHTNESS_INC	0x86
126 #define	VIDEO_NOTIFY_BRIGHTNESS_DEC	0x87
127 #define	VIDEO_NOTIFY_BRIGHTNESS_ZERO	0x88
128 
129 /* Output device status */
130 #define	ACPI_DRV_DCS_CONNECTOR_EXIST	(1 << 0)
131 #define	ACPI_DRV_DCS_ACTIVE		(1 << 1)
132 #define	ACPI_DRV_DCS_READY		(1 << 2)
133 #define	ACPI_DRV_DCS_FUNCTIONAL		(1 << 3)
134 #define	ACPI_DRV_DCS_ATTACHED		(1 << 4)
135 
136 /* _DOS default value is 1 */
137 /* _DOS bit 1:0 */
138 #define	VIDEO_POLICY_SWITCH_OS		0x0
139 #define	VIDEO_POLICY_SWITCH_BIOS	0x1
140 #define	VIDEO_POLICY_SWITCH_LOCKED	0x2
141 #define	VIDEO_POLICY_SWITCH_OS_EVENT	0x3
142 
143 /* _DOS bit 2 */
144 #define	VIDEO_POLICY_BRIGHTNESS_OS	0x4
145 #define	VIDEO_POLICY_BRIGHTNESS_BIOS	0x0
146 
147 /* Set _DOS for video control policy */
148 static void
149 acpi_video_set_dos(struct acpi_video *vidp, uint32_t policy)
150 {
151 	struct acpi_video_switch *vidsp;
152 	ACPI_STATUS status;
153 	ACPI_OBJECT obj;
154 	ACPI_OBJECT_LIST objlist;
155 
156 	obj.Type = ACPI_TYPE_INTEGER;
157 	obj.Integer.Value = policy;
158 	objlist.Count = 1;
159 	objlist.Pointer = &obj;
160 
161 	vidsp = vidp->vid_switch;
162 	while (vidsp != NULL) {
163 		status = AcpiEvaluateObject(vidsp->dev.hdl, ACPI_METHOD_DOS,
164 		    &objlist, NULL);
165 		if (ACPI_FAILURE(status))
166 			cmn_err(CE_WARN, "!acpi_video_set_dos failed.");
167 		vidsp = vidsp->next;
168 	}
169 }
170 
171 /*
172  * Get the current brightness level and index.
173  */
174 static int
175 acpi_video_brightness_get(struct acpi_video_brightness *vidbp)
176 {
177 	int i;
178 
179 	if (acpi_brightness_get_disable) {
180 		/* simply initialize current brightness to the highest level */
181 		vidbp->cur_level_index = vidbp->nlevel - 1;
182 		vidbp->cur_level = vidbp->levels[vidbp->cur_level_index];
183 		return (ACPI_DRV_OK);
184 	}
185 
186 	if (acpica_eval_int(vidbp->dev.hdl, "_BQC", &vidbp->cur_level)
187 	    != AE_OK) {
188 		vidbp->cur_level = 0;
189 		return (ACPI_DRV_ERR);
190 	}
191 
192 	for (i = 0; i < vidbp->nlevel; i++) {
193 		if (vidbp->levels[i] == vidbp->cur_level) {
194 			vidbp->cur_level_index = i;
195 			if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
196 				cmn_err(CE_NOTE, "!acpi_video_brightness_get():"
197 				    " cur_level = %d, cur_level_index = %d\n",
198 				    vidbp->cur_level, i);
199 			}
200 			break;
201 		}
202 	}
203 
204 	return (ACPI_DRV_OK);
205 }
206 
207 static int
208 acpi_video_brightness_set(struct acpi_video_brightness *vidbp, uint32_t level)
209 {
210 	if (acpi_drv_set_int(vidbp->dev.hdl, "_BCM", vidbp->levels[level])
211 	    != AE_OK) {
212 		return (ACPI_DRV_ERR);
213 	}
214 
215 	vidbp->cur_level = vidbp->levels[level];
216 	vidbp->cur_level_index = level;
217 
218 	return (ACPI_DRV_OK);
219 }
220 
221 void
222 hotkey_drv_gen_sysevent(dev_info_t *dip, char *event)
223 {
224 	int err;
225 
226 	/* Generate/log EC_ACPIEV sysevent */
227 	err = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_ACPIEV,
228 	    event, NULL, NULL, DDI_NOSLEEP);
229 
230 	if (err != DDI_SUCCESS) {
231 		cmn_err(CE_WARN,
232 		    "!failed to log hotkey sysevent, err code %x\n", err);
233 	}
234 }
235 
236 /*ARGSUSED*/
237 static void
238 acpi_video_switch_notify(ACPI_HANDLE hdl, uint32_t notify, void *ctx)
239 {
240 	if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
241 		cmn_err(CE_NOTE, "!acpi_video_switch_notify: got event 0x%x.\n",
242 		    notify);
243 	}
244 
245 	mutex_enter(acpi_hotkey.hotkey_lock);
246 	switch (notify) {
247 	case VIDEO_NOTIFY_SWITCH:
248 	case VIDEO_NOTIFY_SWITCH_CYCLE:
249 	case VIDEO_NOTIFY_SWITCH_NEXT:
250 	case VIDEO_NOTIFY_SWITCH_PREV:
251 		hotkey_drv_gen_sysevent(acpi_hotkey.dip,
252 		    ESC_ACPIEV_DISPLAY_SWITCH);
253 		break;
254 
255 	case VIDEO_NOTIFY_SWITCH_STATUS:
256 		break;
257 
258 	default:
259 		if (hotkey_drv_debug) {
260 			cmn_err(CE_NOTE,
261 			    "!acpi_video_switch_notify: unknown event 0x%x.\n",
262 			    notify);
263 		}
264 	}
265 	mutex_exit(acpi_hotkey.hotkey_lock);
266 }
267 
268 /*ARGSUSED*/
269 static void
270 acpi_video_brightness_notify(ACPI_HANDLE hdl, uint32_t notify, void *ctx)
271 {
272 	struct acpi_video_brightness *vidbp = ctx;
273 
274 	if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
275 		cmn_err(CE_NOTE,
276 		    "!acpi_video_brightness_notify: got event 0x%x.\n",
277 		    notify);
278 	}
279 
280 	mutex_enter(acpi_hotkey.hotkey_lock);
281 	switch (notify) {
282 	case VIDEO_NOTIFY_BRIGHTNESS_CYCLE:
283 	case VIDEO_NOTIFY_BRIGHTNESS_INC:
284 		if (vidbp->cur_level_index < vidbp->nlevel - 1) {
285 			if (acpi_video_brightness_set(vidbp,
286 			    vidbp->cur_level_index + 1) != ACPI_DRV_OK) {
287 				break;
288 			}
289 		}
290 		acpi_drv_gen_sysevent(&vidbp->dev, ESC_PWRCTL_BRIGHTNESS_UP, 0);
291 		break;
292 	case VIDEO_NOTIFY_BRIGHTNESS_DEC:
293 		if (vidbp->cur_level_index > 0) {
294 			if (acpi_video_brightness_set(vidbp,
295 			    vidbp->cur_level_index - 1) != ACPI_DRV_OK) {
296 				break;
297 			}
298 		}
299 		acpi_drv_gen_sysevent(&vidbp->dev, ESC_PWRCTL_BRIGHTNESS_DOWN,
300 		    0);
301 		break;
302 	case VIDEO_NOTIFY_BRIGHTNESS_ZERO:
303 		if (acpi_video_brightness_set(vidbp, 0) != ACPI_DRV_OK) {
304 			break;
305 		}
306 		acpi_drv_gen_sysevent(&vidbp->dev, ESC_PWRCTL_BRIGHTNESS_DOWN,
307 		    0);
308 		break;
309 
310 	default:
311 		if (hotkey_drv_debug) {
312 			cmn_err(CE_NOTE, "!acpi_video_brightness_notify: "
313 			    "unknown event 0x%x.\n", notify);
314 		}
315 	}
316 	mutex_exit(acpi_hotkey.hotkey_lock);
317 }
318 
319 static int
320 acpi_video_notify_intall(struct acpi_video *vidp)
321 {
322 	ACPI_STATUS status;
323 	struct acpi_video_switch *vidsp;
324 	struct acpi_video_brightness *vidbp;
325 	int i;
326 
327 	/* bind video switch notify */
328 	vidsp = vidp->vid_switch;
329 	for (i = 0; i < vidp->total_switch && vidsp != NULL; i++) {
330 		status = AcpiInstallNotifyHandler(vidsp->dev.hdl,
331 		    ACPI_DEVICE_NOTIFY, acpi_video_switch_notify, vidsp);
332 		if (ACPI_FAILURE(status)) {
333 			cmn_err(CE_WARN,
334 			    "!vids handler install failed = %d, vids = %p.",
335 			    status, (void *) vidsp);
336 		}
337 		vidsp = vidsp->next;
338 	}
339 
340 	/* bind brightness control notify */
341 	vidbp = vidp->vid_brightness;
342 	for (i = 0; i < vidp->total_brightness && vidbp != NULL; i++) {
343 		status = AcpiInstallNotifyHandler(vidbp->dev.hdl,
344 		    ACPI_DEVICE_NOTIFY, acpi_video_brightness_notify, vidbp);
345 		if (ACPI_FAILURE(status)) {
346 			cmn_err(CE_WARN,
347 			    "!brightness handler install failed = %x, "
348 			    "brightness = %p.", status, (void *) vidbp);
349 		}
350 		vidbp = vidbp->next;
351 	}
352 
353 	return (ACPI_DRV_OK);
354 }
355 
356 static int
357 acpi_video_notify_unintall(struct acpi_video *vidp)
358 {
359 	struct acpi_video_switch *vidsp;
360 	struct acpi_video_brightness *vidbp;
361 	int i;
362 
363 	/* unbind video switch notify */
364 	vidsp = vidp->vid_switch;
365 	for (i = 0; i < vidp->total_switch && vidsp != NULL; i++) {
366 		(void) AcpiRemoveNotifyHandler(vidsp->dev.hdl,
367 		    ACPI_DEVICE_NOTIFY, acpi_video_switch_notify);
368 		vidsp = vidsp->next;
369 	}
370 
371 	/* unbind brightness control notify */
372 	vidbp = vidp->vid_brightness;
373 	for (i = 0; i < vidp->total_brightness && vidbp != NULL; i++) {
374 		(void) AcpiRemoveNotifyHandler(vidbp->dev.hdl,
375 		    ACPI_DEVICE_NOTIFY, acpi_video_brightness_notify);
376 		vidbp = vidbp->next;
377 	}
378 
379 	return (ACPI_DRV_OK);
380 }
381 
382 static int
383 acpi_video_free(struct acpi_video *vidp)
384 {
385 	struct acpi_video_switch *vidsp;
386 	struct acpi_video_switch *vidsp_next;
387 	struct acpi_video_brightness *vidbp;
388 	struct acpi_video_brightness *vidbp_next;
389 	struct acpi_video_output *vidop;
390 	struct acpi_video_output *vidop_next;
391 
392 	/* free video switch objects */
393 	vidsp = vidp->vid_switch;
394 	while (vidsp != NULL) {
395 		vidsp_next = vidsp->next;
396 		kmem_free(vidsp, sizeof (struct acpi_video_switch));
397 		vidsp = vidsp_next;
398 	}
399 
400 	/* free video brightness control objects */
401 	vidbp = vidp->vid_brightness;
402 	while (vidbp != NULL) {
403 		vidbp_next = vidbp->next;
404 		kmem_free(vidbp, sizeof (struct acpi_video_brightness));
405 		vidbp = vidbp_next;
406 	}
407 
408 	/* free video output objects */
409 	vidop = vidp->vid_outputs;
410 	while (vidop != NULL) {
411 		vidop_next = vidop->next;
412 		kmem_free(vidop, sizeof (struct acpi_video_output));
413 		vidop = vidop_next;
414 	}
415 
416 	return (ACPI_DRV_OK);
417 }
418 
419 static int
420 acpi_video_fini(struct acpi_video *vidp)
421 {
422 	(void) acpi_video_notify_unintall(vidp);
423 
424 	return (acpi_video_free(vidp));
425 }
426 
427 static int
428 acpi_video_enum_output(ACPI_HANDLE hdl, struct acpi_video *vidp)
429 {
430 	int adr;
431 	struct acpi_video_brightness *vidbp;
432 	struct acpi_video_output *vidop;
433 	ACPI_BUFFER buf = {ACPI_ALLOCATE_BUFFER, NULL};
434 	ACPI_OBJECT *objp;
435 
436 
437 	if (acpica_eval_int(hdl, "_ADR", &adr) != AE_OK)
438 		return (ACPI_DRV_ERR);
439 
440 	/* Allocate object */
441 	vidop = kmem_zalloc(sizeof (struct acpi_video_output), KM_SLEEP);
442 	vidop->dev.hdl = hdl;
443 	(void) acpi_drv_dev_init(&vidop->dev);
444 	vidop->adr = adr;
445 	vidop->type = adr;
446 	vidop->next = vidp->vid_outputs;
447 	vidp->vid_outputs = vidop;
448 
449 	if (ACPI_SUCCESS(AcpiEvaluateObjectTyped(hdl, "_BCL",
450 	    NULL, &buf, ACPI_TYPE_PACKAGE))) {
451 		int i, j, k, l, m, nlev, tmp;
452 
453 		vidbp = kmem_zalloc(sizeof (struct acpi_video_brightness),
454 		    KM_SLEEP);
455 		vidbp->dev = vidop->dev;
456 		vidop->adr = adr;
457 		vidbp->output_index = vidp->total_outputs;
458 		objp = buf.Pointer;
459 
460 		/*
461 		 * op->nlev will be needed to free op->levels.
462 		 */
463 		vidbp->nlevel = nlev = objp->Package.Count;
464 		vidbp->levels = kmem_zalloc(nlev * sizeof (uint32_t), KM_SLEEP);
465 
466 		/*
467 		 * Get all the supported brightness levels.
468 		 */
469 		for (i = 0; i < nlev; i++) {
470 			ACPI_OBJECT *o = &objp->Package.Elements[i];
471 			int lev = o->Integer.Value;
472 
473 			if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
474 				cmn_err(CE_NOTE, "!acpi_video_enum_output() "
475 				    "brlev=%d i=%d nlev=%d\n", lev, i, nlev);
476 			}
477 			if (o->Type != ACPI_TYPE_INTEGER) {
478 				continue;
479 			}
480 			vidbp->levels[i] = lev;
481 		}
482 
483 		/*
484 		 * Sort the brightness levels.
485 		 */
486 		for (j = 0; j < nlev; j++) {
487 			for (k = 0; k < nlev - 1; k++) {
488 				if (vidbp->levels[k] > vidbp->levels[k+1]) {
489 					tmp = vidbp->levels[k+1];
490 					vidbp->levels[k+1] = vidbp->levels[k];
491 					vidbp->levels[k] = tmp;
492 				}
493 			}
494 		}
495 
496 		/*
497 		 * The first two levels could be duplicated, so remove
498 		 * any duplicates.
499 		 */
500 		for (l = 0; l < nlev - 1; l++) {
501 			if (vidbp->levels[l] == vidbp->levels[l+1]) {
502 				for (m = l + 1; m < nlev - 1; m++) {
503 					vidbp->levels[m] = vidbp->levels[m+1];
504 				}
505 				nlev--;
506 			}
507 		}
508 
509 		vidbp->nlevel = nlev;
510 		(void) acpi_video_brightness_get(vidbp);
511 		vidbp->next = vidp->vid_brightness;
512 		vidp->vid_brightness = vidbp;
513 		vidp->total_brightness++;
514 
515 		AcpiOsFree(objp);
516 	}
517 
518 	vidp->total_outputs++;
519 
520 	return (ACPI_DRV_OK);
521 }
522 
523 /*ARGSUSED*/
524 static ACPI_STATUS
525 acpi_video_find_and_alloc(ACPI_HANDLE hdl, UINT32 nest, void *ctx,
526     void **rv)
527 {
528 	ACPI_HANDLE tmphdl;
529 	ACPI_STATUS err;
530 	ACPI_BUFFER buf = {ACPI_ALLOCATE_BUFFER, NULL};
531 	struct acpi_video *vidp;
532 	struct acpi_video_switch *vidsp;
533 
534 	err = AcpiGetHandle(hdl, ACPI_METHOD_DOS, &tmphdl);
535 	if (err != AE_OK)
536 		return (AE_OK);
537 
538 	err = AcpiGetHandle(hdl, ACPI_METHOD_DOD, &tmphdl);
539 	if (err != AE_OK)
540 		return (AE_OK);
541 
542 	vidp = (struct acpi_video *)ctx;
543 	vidsp = kmem_zalloc(sizeof (struct acpi_video_switch), KM_SLEEP);
544 	vidsp->dev.hdl = hdl;
545 	(void) acpi_drv_dev_init(&vidsp->dev);
546 	vidsp->next = vidp->vid_switch;
547 	vidp->vid_switch = vidsp;
548 	vidp->total_switch++;
549 
550 	/*
551 	 * Enumerate the output devices.
552 	 */
553 	while (ACPI_SUCCESS(AcpiGetNextObject(ACPI_TYPE_DEVICE,
554 	    hdl, tmphdl, &tmphdl))) {
555 		(void) acpi_video_enum_output(tmphdl, vidp);
556 	}
557 
558 	if (!ACPI_FAILURE(AcpiGetName(hdl, ACPI_FULL_PATHNAME, &buf))) {
559 		if (buf.Pointer) {
560 			if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
561 				cmn_err(CE_NOTE,
562 				    "!acpi video switch hdl = 0x%p, path = %s.",
563 				    hdl, (char *)buf.Pointer);
564 			}
565 			AcpiOsFree(buf.Pointer);
566 		}
567 	}
568 
569 	return (AE_OK);
570 }
571 
572 int
573 hotkey_brightness_inc(hotkey_drv_t *htkp)
574 {
575 	struct acpi_video *vidp;
576 	struct acpi_video_brightness *vidbp;
577 
578 	vidp = (struct acpi_video *)htkp->acpi_video;
579 
580 	for (vidbp = vidp->vid_brightness; vidbp != NULL; vidbp = vidbp->next) {
581 		if (vidbp->cur_level_index < vidbp->nlevel - 1) {
582 			if (acpi_video_brightness_set(vidbp,
583 			    vidbp->cur_level_index + 1) != ACPI_DRV_OK) {
584 				return (ACPI_DRV_ERR);
585 			}
586 		}
587 	}
588 	return (ACPI_DRV_OK);
589 }
590 
591 int
592 hotkey_brightness_dec(hotkey_drv_t *htkp)
593 {
594 	struct acpi_video *vidp;
595 	struct acpi_video_brightness *vidbp;
596 
597 	vidp = (struct acpi_video *)htkp->acpi_video;
598 
599 	for (vidbp = vidp->vid_brightness; vidbp != NULL; vidbp = vidbp->next) {
600 		if (vidbp->cur_level_index > 0) {
601 			if (acpi_video_brightness_set(vidbp,
602 			    vidbp->cur_level_index - 1) != ACPI_DRV_OK) {
603 				return (ACPI_DRV_ERR);
604 			}
605 		}
606 	}
607 
608 	return (ACPI_DRV_OK);
609 }
610 
611 /*ARGSUSED*/
612 int
613 acpi_video_ioctl(void *p, int cmd, intptr_t arg, int mode, cred_t *cr,
614     int *rval)
615 {
616 	struct acpi_video *vidp = p;
617 	struct acpi_video_brightness *vidbp;
618 	int res = 0;
619 
620 	if (vidp == NULL)
621 		return (ENXIO);
622 
623 	vidbp = vidp->vid_brightness;
624 	if (vidbp == NULL)
625 		return (ENXIO);
626 
627 	if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
628 		cmn_err(CE_NOTE, "!acpi_video_ioctl cmd %d\n", cmd);
629 	}
630 
631 	switch (cmd) {
632 	case ACPI_DRV_IOC_INFO:
633 	{
634 		struct acpi_drv_output_info inf;
635 
636 		inf.adr = vidbp->adr;
637 		inf.nlev = vidbp->nlevel;
638 		if (copyout(&inf, (void *)arg, sizeof (inf))) {
639 			res = EFAULT;
640 		}
641 		break;
642 	}
643 
644 	case ACPI_DRV_IOC_LEVELS:
645 		if (copyout(vidbp->levels, (void *)arg,
646 		    sizeof (*vidbp->levels) * vidbp->nlevel)) {
647 			res = EFAULT;
648 		}
649 		break;
650 
651 	case ACPI_DRV_IOC_STATUS:
652 	{
653 		/*
654 		 * Need to get the current levels through ACPI first
655 		 * then go through array of levels to find index.
656 		 */
657 		struct acpi_drv_output_status status;
658 		int i;
659 
660 		status.state = 0;
661 		status.num_levels = vidbp->nlevel;
662 		status.cur_level = vidbp->cur_level;
663 		for (i = 0; i < vidbp->nlevel; i++) {
664 			if (vidbp->levels[i] == vidbp->cur_level) {
665 				status.cur_level_index = i;
666 				if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
667 					cmn_err(CE_NOTE, "!ACPI_DRV_IOC_STATUS "
668 					    "cur_level_index %d\n", i);
669 				}
670 				break;
671 			}
672 		}
673 		if (copyout(&status, (void *)arg, sizeof (status))) {
674 			res = EFAULT;
675 		}
676 		break;
677 	}
678 
679 	case ACPI_DRV_IOC_SET_BRIGHTNESS: {
680 		int level;
681 
682 		if (drv_priv(cr)) {
683 			res = EPERM;
684 			break;
685 		}
686 		if (copyin((void *)arg, &level, sizeof (level))) {
687 			res = EFAULT;
688 			break;
689 		}
690 		if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
691 			cmn_err(CE_NOTE,
692 			    "!acpi_video_ioctl: set BRIGHTNESS level=%d\n",
693 			    level);
694 		}
695 		if (acpi_video_brightness_set(vidbp, level) != ACPI_DRV_OK) {
696 			res = EFAULT;
697 		}
698 		break;
699 	}
700 
701 	default:
702 		res = EINVAL;
703 		break;
704 	}
705 
706 	return (res);
707 }
708 
709 /*ARGSUSED*/
710 int
711 acpi_drv_hotkey_ioctl(int cmd, intptr_t arg, int mode, cred_t *cr,
712     int *rval)
713 {
714 	hotkey_drv_t *htkp = &acpi_hotkey;
715 
716 	switch (htkp->hotkey_method) {
717 	case HOTKEY_METHOD_ACPI_VIDEO:
718 		return (acpi_video_ioctl(htkp->acpi_video, cmd, arg, mode,
719 		    cr, rval));
720 	case HOTKEY_METHOD_MISC:
721 	case HOTKEY_METHOD_VENDOR:
722 		return (htkp->vendor_ioctl(htkp, cmd, arg, mode, cr, rval));
723 	case HOTKEY_METHOD_NONE:
724 	default:
725 		return (ENXIO);
726 	}
727 }
728 
729 static void
730 acpi_video_check_blacklist(void)
731 {
732 	smbios_hdl_t *smhdl = NULL;
733 	id_t smid;
734 	smbios_system_t smsys;
735 	smbios_info_t sminfo;
736 	char *mfg, *product;
737 	struct acpi_video_smbios_info *pblacklist;
738 
739 	acpi_brightness_get_disable = 0;
740 	smhdl = smbios_open(NULL, SMB_VERSION, ksmbios_flags, NULL);
741 	if (smhdl == NULL ||
742 	    ((smid = smbios_info_system(smhdl, &smsys)) == SMB_ERR) ||
743 	    (smbios_info_common(smhdl, smid, &sminfo) == SMB_ERR)) {
744 		goto done;
745 	}
746 
747 	mfg = (char *)sminfo.smbi_manufacturer;
748 	product = (char *)sminfo.smbi_product;
749 	for (pblacklist = acpi_brightness_get_blacklist;
750 	    pblacklist->manufacturer != NULL; pblacklist++) {
751 		if ((strcmp(mfg, pblacklist->manufacturer) == 0) &&
752 		    (strcmp(product, pblacklist->product) == 0)) {
753 			acpi_brightness_get_disable = 1;
754 		}
755 	}
756 done:
757 	if (smhdl != NULL)
758 		smbios_close(smhdl);
759 }
760 
761 static int
762 hotkey_acpi_video_check(hotkey_drv_t *htkp)
763 {
764 	struct acpi_video *vidp;
765 
766 	vidp = &acpi_video_hotkey;
767 	bzero(vidp, sizeof (struct acpi_video));
768 	if (acpi_brightness_get_disable == -1)
769 		acpi_video_check_blacklist();
770 	/* Find ACPI Video device handle */
771 	if (ACPI_FAILURE(AcpiGetDevices(NULL, acpi_video_find_and_alloc,
772 	    vidp, NULL))) {
773 		return (ACPI_DRV_ERR);
774 	}
775 
776 	htkp->acpi_video = vidp;
777 	if (htkp->hotkey_method == HOTKEY_METHOD_NONE) {
778 		if (acpi_video_notify_intall(vidp) != ACPI_DRV_OK) {
779 			(void) acpi_video_fini(vidp);
780 			htkp->acpi_video = NULL;
781 			return (ACPI_DRV_ERR);
782 		}
783 	}
784 	htkp->hotkey_method |= HOTKEY_METHOD_ACPI_VIDEO;
785 
786 	acpi_video_set_dos(vidp, VIDEO_POLICY_BRIGHTNESS_OS |
787 	    VIDEO_POLICY_SWITCH_OS);
788 
789 	return (ACPI_DRV_OK);
790 }
791 
792 int
793 hotkey_init(hotkey_drv_t *htkp)
794 {
795 	int i;
796 	int modid;
797 	modctl_t *modp;
798 
799 	htkp->modid = -1;
800 	/* Try to find vendor specific method */
801 	for (i = 0; vendor_hotkey_drv_list[i].module != NULL; i++) {
802 		if (!vendor_hotkey_drv_list[i].enable)
803 			continue;
804 
805 		if ((modid = modload("drv", vendor_hotkey_drv_list[i].module))
806 		    == -1) {
807 			continue;
808 		}
809 
810 		htkp->modid = modid;
811 		if (hotkey_drv_debug & HOTKEY_DBG_NOTICE) {
812 			cmn_err(CE_NOTE, "!loaded %s specific method.\n",
813 			    vendor_hotkey_drv_list[i].vid);
814 		}
815 	}
816 
817 	/* Check availability of ACPI Video Extension method */
818 	if (htkp->hotkey_method == HOTKEY_METHOD_NONE ||
819 	    htkp->check_acpi_video) {
820 		if (hotkey_acpi_video_check(htkp) == ACPI_DRV_OK) {
821 			if (hotkey_drv_debug & HOTKEY_DBG_NOTICE)
822 				cmn_err(CE_NOTE, "!find ACPI video method.\n");
823 		} else
824 			goto fail;
825 	}
826 
827 	if (htkp->modid != -1) {
828 		modp = mod_hold_by_id(htkp->modid);
829 		mutex_enter(&mod_lock);
830 		modp->mod_ref = 1;
831 		modp->mod_loadflags |= MOD_NOAUTOUNLOAD;
832 		mutex_exit(&mod_lock);
833 		mod_release_mod(modp);
834 	}
835 
836 	return (ACPI_DRV_OK);
837 
838 fail:
839 	if (htkp->vendor_fini != NULL)
840 		htkp->vendor_fini(htkp);
841 	if (htkp->modid != -1)
842 		(void) modunload(htkp->modid);
843 
844 	return (ACPI_DRV_ERR);
845 }
846 
847 
848 int
849 hotkey_fini(hotkey_drv_t *htkp)
850 {
851 	modctl_t *modp;
852 
853 	if (htkp->vendor_fini != NULL)
854 		htkp->vendor_fini(htkp);
855 	if (htkp->acpi_video != NULL)
856 		(void) acpi_video_fini(htkp->acpi_video);
857 	if (htkp->modid != -1) {
858 		modp = mod_hold_by_id(htkp->modid);
859 		mutex_enter(&mod_lock);
860 		modp->mod_ref = 0;
861 		modp->mod_loadflags &= ~MOD_NOAUTOUNLOAD;
862 		mutex_exit(&mod_lock);
863 		mod_release_mod(modp);
864 		(void) modunload(htkp->modid);
865 	}
866 
867 	return (ACPI_DRV_OK);
868 }
869