17c478bd9Sstevel@tonic-gate /*
28e56767dSsmall * CDDL HEADER START
38e56767dSsmall *
48e56767dSsmall * The contents of this file are subject to the terms of the
527f7c583Smyers * Common Development and Distribution License (the "License").
627f7c583Smyers * You may not use this file except in compliance with the License.
78e56767dSsmall *
88e56767dSsmall * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
98e56767dSsmall * or http://www.opensolaris.org/os/licensing.
108e56767dSsmall * See the License for the specific language governing permissions
118e56767dSsmall * and limitations under the License.
128e56767dSsmall *
138e56767dSsmall * When distributing Covered Code, include this CDDL HEADER in each
148e56767dSsmall * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
158e56767dSsmall * If applicable, add the following below this CDDL HEADER, with the
168e56767dSsmall * fields enclosed by brackets "[]" replaced with your own identifying
178e56767dSsmall * information: Portions Copyright [yyyy] [name of copyright owner]
188e56767dSsmall *
198e56767dSsmall * CDDL HEADER END
207c478bd9Sstevel@tonic-gate */
217c478bd9Sstevel@tonic-gate /*
22a3463f0aSDana Myers * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
237c478bd9Sstevel@tonic-gate * Use is subject to license terms.
24*26f3cdf0SGordon Ross * Copyright 2011 Joyent, Inc. All rights reserved.
25*26f3cdf0SGordon Ross * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
267c478bd9Sstevel@tonic-gate */
278e56767dSsmall /*
288e56767dSsmall * Solaris x86 ACPI CA Embedded Controller operation region handler
298e56767dSsmall */
307c478bd9Sstevel@tonic-gate
317c478bd9Sstevel@tonic-gate #include <sys/file.h>
327c478bd9Sstevel@tonic-gate #include <sys/errno.h>
337c478bd9Sstevel@tonic-gate #include <sys/conf.h>
347c478bd9Sstevel@tonic-gate #include <sys/modctl.h>
357c478bd9Sstevel@tonic-gate #include <sys/open.h>
367c478bd9Sstevel@tonic-gate #include <sys/stat.h>
377c478bd9Sstevel@tonic-gate #include <sys/ddi.h>
387c478bd9Sstevel@tonic-gate #include <sys/sunddi.h>
397c478bd9Sstevel@tonic-gate #include <sys/note.h>
40a3463f0aSDana Myers #include <sys/atomic.h>
417c478bd9Sstevel@tonic-gate
427c478bd9Sstevel@tonic-gate #include <sys/acpi/acpi.h>
437c478bd9Sstevel@tonic-gate #include <sys/acpica.h>
447c478bd9Sstevel@tonic-gate
457c478bd9Sstevel@tonic-gate /*
467c478bd9Sstevel@tonic-gate * EC status bits
47*26f3cdf0SGordon Ross * Low to high
48*26f3cdf0SGordon Ross * Output buffer full?
49*26f3cdf0SGordon Ross * Input buffer full?
50*26f3cdf0SGordon Ross * <reserved>
51*26f3cdf0SGordon Ross * Data register is command byte?
52*26f3cdf0SGordon Ross * Burst mode enabled?
53*26f3cdf0SGordon Ross * SCI event?
54*26f3cdf0SGordon Ross * SMI event?
55*26f3cdf0SGordon Ross * <reserved>
567c478bd9Sstevel@tonic-gate */
577c478bd9Sstevel@tonic-gate #define EC_OBF (0x01)
58*26f3cdf0SGordon Ross #define EC_IBF (0x02)
59*26f3cdf0SGordon Ross #define EC_DRC (0x08)
60*26f3cdf0SGordon Ross #define EC_BME (0x10)
617c478bd9Sstevel@tonic-gate #define EC_SCI (0x20)
62*26f3cdf0SGordon Ross #define EC_SMI (0x40)
637c478bd9Sstevel@tonic-gate
647c478bd9Sstevel@tonic-gate /*
657c478bd9Sstevel@tonic-gate * EC commands
667c478bd9Sstevel@tonic-gate */
677c478bd9Sstevel@tonic-gate #define EC_RD (0x80)
687c478bd9Sstevel@tonic-gate #define EC_WR (0x81)
697c478bd9Sstevel@tonic-gate #define EC_BE (0x82)
707c478bd9Sstevel@tonic-gate #define EC_BD (0x83)
717c478bd9Sstevel@tonic-gate #define EC_QR (0x84)
727c478bd9Sstevel@tonic-gate
737c478bd9Sstevel@tonic-gate #define IO_PORT_DES (0x47)
747c478bd9Sstevel@tonic-gate
757c478bd9Sstevel@tonic-gate /*
767c478bd9Sstevel@tonic-gate * EC softstate
777c478bd9Sstevel@tonic-gate */
78*26f3cdf0SGordon Ross static struct ec_softstate {
79*26f3cdf0SGordon Ross uint8_t ec_ok; /* != 0 if we have ec_base, ec_sc */
807c478bd9Sstevel@tonic-gate uint16_t ec_base; /* base of EC I/O port - data */
81*26f3cdf0SGordon Ross uint16_t ec_sc; /* EC status/command */
82*26f3cdf0SGordon Ross ACPI_HANDLE ec_dev_hdl; /* EC device handle */
83*26f3cdf0SGordon Ross ACPI_HANDLE ec_gpe_hdl; /* GPE info */
84*26f3cdf0SGordon Ross ACPI_INTEGER ec_gpe_bit;
85450d6964Smyers kmutex_t ec_mutex; /* serialize access to EC */
867c478bd9Sstevel@tonic-gate } ec;
877c478bd9Sstevel@tonic-gate
887c478bd9Sstevel@tonic-gate /* I/O port range descriptor */
897c478bd9Sstevel@tonic-gate typedef struct io_port_des {
907c478bd9Sstevel@tonic-gate uint8_t type;
917c478bd9Sstevel@tonic-gate uint8_t decode;
927c478bd9Sstevel@tonic-gate uint8_t min_base_lo;
937c478bd9Sstevel@tonic-gate uint8_t min_base_hi;
947c478bd9Sstevel@tonic-gate uint8_t max_base_lo;
957c478bd9Sstevel@tonic-gate uint8_t max_base_hi;
967c478bd9Sstevel@tonic-gate uint8_t align;
977c478bd9Sstevel@tonic-gate uint8_t len;
987c478bd9Sstevel@tonic-gate } io_port_des_t;
997c478bd9Sstevel@tonic-gate
100*26f3cdf0SGordon Ross /*
101*26f3cdf0SGordon Ross * Patchable to ignore an ECDT, in case using that
102*26f3cdf0SGordon Ross * causes problems on someone's system.
103*26f3cdf0SGordon Ross */
104*26f3cdf0SGordon Ross int ec_ignore_ecdt = 0;
105*26f3cdf0SGordon Ross
106a3463f0aSDana Myers /*
107a3463f0aSDana Myers * Patchable timeout values for EC input-buffer-full-clear
108a3463f0aSDana Myers * and output-buffer-full-set. These are in 10uS units and
109a3463f0aSDana Myers * default to 1 second.
110a3463f0aSDana Myers */
111a3463f0aSDana Myers int ibf_clear_timeout = 100000;
112a3463f0aSDana Myers int obf_set_timeout = 100000;
113a3463f0aSDana Myers
1147c478bd9Sstevel@tonic-gate /*
115*26f3cdf0SGordon Ross * ACPI CA EC address space handler support functions
1167c478bd9Sstevel@tonic-gate */
117*26f3cdf0SGordon Ross
118*26f3cdf0SGordon Ross /*
119*26f3cdf0SGordon Ross * Busy-wait for IBF to clear
120*26f3cdf0SGordon Ross * return < 0 for time out, 0 for no error
121*26f3cdf0SGordon Ross */
122*26f3cdf0SGordon Ross static int
ec_wait_ibf_clear(int sc_addr)123*26f3cdf0SGordon Ross ec_wait_ibf_clear(int sc_addr)
1247c478bd9Sstevel@tonic-gate {
125*26f3cdf0SGordon Ross int cnt;
1267c478bd9Sstevel@tonic-gate
127*26f3cdf0SGordon Ross cnt = ibf_clear_timeout;
128*26f3cdf0SGordon Ross while (inb(sc_addr) & EC_IBF) {
129*26f3cdf0SGordon Ross if (cnt-- <= 0)
130*26f3cdf0SGordon Ross return (-1);
131*26f3cdf0SGordon Ross drv_usecwait(10);
132*26f3cdf0SGordon Ross }
133*26f3cdf0SGordon Ross return (0);
134*26f3cdf0SGordon Ross }
135*26f3cdf0SGordon Ross
136*26f3cdf0SGordon Ross /*
137*26f3cdf0SGordon Ross * Busy-wait for OBF to set
138*26f3cdf0SGordon Ross * return < 0 for time out, 0 for no error
139*26f3cdf0SGordon Ross */
140*26f3cdf0SGordon Ross static int
ec_wait_obf_set(int sc_addr)141*26f3cdf0SGordon Ross ec_wait_obf_set(int sc_addr)
142*26f3cdf0SGordon Ross {
143*26f3cdf0SGordon Ross int cnt;
144*26f3cdf0SGordon Ross
145*26f3cdf0SGordon Ross cnt = obf_set_timeout;
146*26f3cdf0SGordon Ross while (!(inb(sc_addr) & EC_OBF)) {
147*26f3cdf0SGordon Ross if (cnt-- <= 0)
148*26f3cdf0SGordon Ross return (-1);
149*26f3cdf0SGordon Ross drv_usecwait(10);
150*26f3cdf0SGordon Ross }
151*26f3cdf0SGordon Ross return (0);
1527c478bd9Sstevel@tonic-gate }
1537c478bd9Sstevel@tonic-gate
154a3463f0aSDana Myers /*
155a3463f0aSDana Myers * Only called from ec_handler(), which validates ec_ok
156a3463f0aSDana Myers */
1577c478bd9Sstevel@tonic-gate static int
ec_rd(int addr)1587c478bd9Sstevel@tonic-gate ec_rd(int addr)
1597c478bd9Sstevel@tonic-gate {
160450d6964Smyers int cnt, rv;
161450d6964Smyers uint8_t sc;
162450d6964Smyers
163450d6964Smyers mutex_enter(&ec.ec_mutex);
164450d6964Smyers sc = inb(ec.ec_sc);
1657c478bd9Sstevel@tonic-gate
1667c478bd9Sstevel@tonic-gate #ifdef DEBUG
1677c478bd9Sstevel@tonic-gate if (sc & EC_IBF) {
1687c478bd9Sstevel@tonic-gate cmn_err(CE_NOTE, "!ec_rd: IBF already set");
1697c478bd9Sstevel@tonic-gate }
1707c478bd9Sstevel@tonic-gate
1717c478bd9Sstevel@tonic-gate if (sc & EC_OBF) {
1727c478bd9Sstevel@tonic-gate cmn_err(CE_NOTE, "!ec_rd: OBF already set");
1737c478bd9Sstevel@tonic-gate }
1747c478bd9Sstevel@tonic-gate #endif
1757c478bd9Sstevel@tonic-gate
1767c478bd9Sstevel@tonic-gate outb(ec.ec_sc, EC_RD); /* output a read command */
177450d6964Smyers if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
178450d6964Smyers cmn_err(CE_NOTE, "!ec_rd:1: timed-out waiting "
179450d6964Smyers "for IBF to clear");
180450d6964Smyers mutex_exit(&ec.ec_mutex);
181450d6964Smyers return (-1);
1827c478bd9Sstevel@tonic-gate }
1837c478bd9Sstevel@tonic-gate
1847c478bd9Sstevel@tonic-gate outb(ec.ec_base, addr); /* output addr */
185450d6964Smyers if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
186450d6964Smyers cmn_err(CE_NOTE, "!ec_rd:2: timed-out waiting "
187450d6964Smyers "for IBF to clear");
188450d6964Smyers mutex_exit(&ec.ec_mutex);
189450d6964Smyers return (-1);
1907c478bd9Sstevel@tonic-gate }
191450d6964Smyers if (ec_wait_obf_set(ec.ec_sc) < 0) {
192450d6964Smyers cmn_err(CE_NOTE, "!ec_rd:1: timed-out waiting "
193450d6964Smyers "for OBF to set");
194450d6964Smyers mutex_exit(&ec.ec_mutex);
195450d6964Smyers return (-1);
1967c478bd9Sstevel@tonic-gate }
1977c478bd9Sstevel@tonic-gate
198450d6964Smyers rv = inb(ec.ec_base);
199450d6964Smyers mutex_exit(&ec.ec_mutex);
200450d6964Smyers return (rv);
2017c478bd9Sstevel@tonic-gate }
2027c478bd9Sstevel@tonic-gate
203a3463f0aSDana Myers /*
204a3463f0aSDana Myers * Only called from ec_handler(), which validates ec_ok
205a3463f0aSDana Myers */
2067c478bd9Sstevel@tonic-gate static int
ec_wr(int addr,uint8_t val)207*26f3cdf0SGordon Ross ec_wr(int addr, uint8_t val)
2087c478bd9Sstevel@tonic-gate {
2097c478bd9Sstevel@tonic-gate int cnt;
210450d6964Smyers uint8_t sc;
211450d6964Smyers
212450d6964Smyers mutex_enter(&ec.ec_mutex);
213450d6964Smyers sc = inb(ec.ec_sc);
2147c478bd9Sstevel@tonic-gate
2157c478bd9Sstevel@tonic-gate #ifdef DEBUG
2167c478bd9Sstevel@tonic-gate if (sc & EC_IBF) {
2177c478bd9Sstevel@tonic-gate cmn_err(CE_NOTE, "!ec_wr: IBF already set");
2187c478bd9Sstevel@tonic-gate }
2197c478bd9Sstevel@tonic-gate
2207c478bd9Sstevel@tonic-gate if (sc & EC_OBF) {
2217c478bd9Sstevel@tonic-gate cmn_err(CE_NOTE, "!ec_wr: OBF already set");
2227c478bd9Sstevel@tonic-gate }
2237c478bd9Sstevel@tonic-gate #endif
2247c478bd9Sstevel@tonic-gate
2257c478bd9Sstevel@tonic-gate outb(ec.ec_sc, EC_WR); /* output a write command */
226450d6964Smyers if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
227450d6964Smyers cmn_err(CE_NOTE, "!ec_wr:1: timed-out waiting "
228450d6964Smyers "for IBF to clear");
229450d6964Smyers mutex_exit(&ec.ec_mutex);
230450d6964Smyers return (-1);
2317c478bd9Sstevel@tonic-gate }
2327c478bd9Sstevel@tonic-gate
2337c478bd9Sstevel@tonic-gate outb(ec.ec_base, addr); /* output addr */
234450d6964Smyers if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
235450d6964Smyers cmn_err(CE_NOTE, "!ec_wr:2: timed-out waiting "
236450d6964Smyers "for IBF to clear");
237450d6964Smyers mutex_exit(&ec.ec_mutex);
238450d6964Smyers return (-1);
2397c478bd9Sstevel@tonic-gate }
2407c478bd9Sstevel@tonic-gate
241*26f3cdf0SGordon Ross outb(ec.ec_base, val); /* write data */
242450d6964Smyers if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
243450d6964Smyers cmn_err(CE_NOTE, "!ec_wr:3: timed-out waiting "
244450d6964Smyers "for IBF to clear");
245450d6964Smyers mutex_exit(&ec.ec_mutex);
246450d6964Smyers return (-1);
2477c478bd9Sstevel@tonic-gate }
2487c478bd9Sstevel@tonic-gate
249450d6964Smyers mutex_exit(&ec.ec_mutex);
2507c478bd9Sstevel@tonic-gate return (0);
2517c478bd9Sstevel@tonic-gate }
2527c478bd9Sstevel@tonic-gate
253a3463f0aSDana Myers /*
254a3463f0aSDana Myers * Only called from ec_gpe_callback(), which validates ec_ok
255a3463f0aSDana Myers */
2567c478bd9Sstevel@tonic-gate static int
ec_query(void)2577c478bd9Sstevel@tonic-gate ec_query(void)
2587c478bd9Sstevel@tonic-gate {
259450d6964Smyers int cnt, rv;
260450d6964Smyers uint8_t sc;
2617c478bd9Sstevel@tonic-gate
262450d6964Smyers mutex_enter(&ec.ec_mutex);
2637c478bd9Sstevel@tonic-gate outb(ec.ec_sc, EC_QR); /* output a query command */
264450d6964Smyers if (ec_wait_ibf_clear(ec.ec_sc) < 0) {
265450d6964Smyers cmn_err(CE_NOTE, "!ec_query:1: timed-out waiting "
266450d6964Smyers "for IBF to clear");
267450d6964Smyers mutex_exit(&ec.ec_mutex);
268450d6964Smyers return (-1);
2697c478bd9Sstevel@tonic-gate }
2707c478bd9Sstevel@tonic-gate
271450d6964Smyers if (ec_wait_obf_set(ec.ec_sc) < 0) {
272450d6964Smyers cmn_err(CE_NOTE, "!ec_query:1: timed-out waiting "
273450d6964Smyers "for OBF to set");
274450d6964Smyers mutex_exit(&ec.ec_mutex);
275450d6964Smyers return (-1);
2767c478bd9Sstevel@tonic-gate }
2777c478bd9Sstevel@tonic-gate
278450d6964Smyers rv = inb(ec.ec_base);
279450d6964Smyers mutex_exit(&ec.ec_mutex);
280450d6964Smyers return (rv);
2817c478bd9Sstevel@tonic-gate }
2827c478bd9Sstevel@tonic-gate
283*26f3cdf0SGordon Ross /*
284*26f3cdf0SGordon Ross * ACPI CA EC address space handler
285*26f3cdf0SGordon Ross * Requires: ec.ec_sc, ec.ec_base
286*26f3cdf0SGordon Ross */
2877c478bd9Sstevel@tonic-gate static ACPI_STATUS
ec_handler(UINT32 func,ACPI_PHYSICAL_ADDRESS addr,UINT32 width,UINT64 * val,void * context,void * regcontext)2887c478bd9Sstevel@tonic-gate ec_handler(UINT32 func, ACPI_PHYSICAL_ADDRESS addr, UINT32 width,
289*26f3cdf0SGordon Ross UINT64 *val, void *context, void *regcontext)
2907c478bd9Sstevel@tonic-gate {
291450d6964Smyers _NOTE(ARGUNUSED(context, regcontext))
292*26f3cdf0SGordon Ross int i, tw, tmp;
2937c478bd9Sstevel@tonic-gate
294a3463f0aSDana Myers /* Guard against unexpected invocation */
295a3463f0aSDana Myers if (ec.ec_ok == 0)
296a3463f0aSDana Myers return (AE_ERROR);
297a3463f0aSDana Myers
298450d6964Smyers /*
299450d6964Smyers * Add safety checks for BIOSes not strictly compliant
300450d6964Smyers * with ACPI spec
301450d6964Smyers */
302*26f3cdf0SGordon Ross if ((width % 8) != 0) {
303a3463f0aSDana Myers cmn_err(CE_NOTE, "!ec_handler: invalid width %d", width);
304*26f3cdf0SGordon Ross return (AE_BAD_PARAMETER);
305*26f3cdf0SGordon Ross }
306*26f3cdf0SGordon Ross if (val == NULL) {
307*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!ec_handler: NULL value pointer");
308*26f3cdf0SGordon Ross return (AE_BAD_PARAMETER);
309450d6964Smyers }
310450d6964Smyers
311*26f3cdf0SGordon Ross while (width > 0) {
312*26f3cdf0SGordon Ross
313*26f3cdf0SGordon Ross /* One UINT64 *val at a time. */
314*26f3cdf0SGordon Ross tw = min(width, 64);
315*26f3cdf0SGordon Ross
316*26f3cdf0SGordon Ross if (func == ACPI_READ)
317*26f3cdf0SGordon Ross *val = 0;
318*26f3cdf0SGordon Ross
319*26f3cdf0SGordon Ross /* Do I/O of up to 64 bits */
320*26f3cdf0SGordon Ross for (i = 0; i < tw; i += 8, addr++) {
321*26f3cdf0SGordon Ross switch (func) {
322*26f3cdf0SGordon Ross case ACPI_READ:
323*26f3cdf0SGordon Ross tmp = ec_rd(addr);
324*26f3cdf0SGordon Ross if (tmp < 0)
325*26f3cdf0SGordon Ross return (AE_ERROR);
326*26f3cdf0SGordon Ross *val |= ((UINT64)tmp) << i;
327*26f3cdf0SGordon Ross break;
328*26f3cdf0SGordon Ross case ACPI_WRITE:
329*26f3cdf0SGordon Ross tmp = ((*val) >> i) & 0xFF;
330*26f3cdf0SGordon Ross if (ec_wr(addr, (uint8_t)tmp) < 0)
331*26f3cdf0SGordon Ross return (AE_ERROR);
332*26f3cdf0SGordon Ross break;
333*26f3cdf0SGordon Ross default:
334*26f3cdf0SGordon Ross return (AE_ERROR);
335*26f3cdf0SGordon Ross }
336*26f3cdf0SGordon Ross }
337*26f3cdf0SGordon Ross val++;
338*26f3cdf0SGordon Ross width -= tw;
3397c478bd9Sstevel@tonic-gate }
3407c478bd9Sstevel@tonic-gate
3417c478bd9Sstevel@tonic-gate return (AE_OK);
3427c478bd9Sstevel@tonic-gate }
3437c478bd9Sstevel@tonic-gate
344*26f3cdf0SGordon Ross /*
345*26f3cdf0SGordon Ross * Called via taskq entry enqueued by ec_gpe_handler,
346*26f3cdf0SGordon Ross * which validates ec_ok
347*26f3cdf0SGordon Ross */
3487c478bd9Sstevel@tonic-gate static void
ec_gpe_callback(void * ctx)3497c478bd9Sstevel@tonic-gate ec_gpe_callback(void *ctx)
3507c478bd9Sstevel@tonic-gate {
3517c478bd9Sstevel@tonic-gate _NOTE(ARGUNUSED(ctx))
3527c478bd9Sstevel@tonic-gate char query_str[5];
353450d6964Smyers int query;
354450d6964Smyers
355450d6964Smyers if (!(inb(ec.ec_sc) & EC_SCI))
356*26f3cdf0SGordon Ross goto out;
357a3463f0aSDana Myers
358450d6964Smyers query = ec_query();
359*26f3cdf0SGordon Ross if (query < 0)
360*26f3cdf0SGordon Ross goto out;
361*26f3cdf0SGordon Ross
362*26f3cdf0SGordon Ross (void) snprintf(query_str, 5, "_Q%02X", (uint8_t)query);
363*26f3cdf0SGordon Ross (void) AcpiEvaluateObject(ec.ec_dev_hdl, query_str, NULL, NULL);
3647c478bd9Sstevel@tonic-gate
365*26f3cdf0SGordon Ross out:
366*26f3cdf0SGordon Ross AcpiFinishGpe(ec.ec_gpe_hdl, ec.ec_gpe_bit);
3677c478bd9Sstevel@tonic-gate }
3687c478bd9Sstevel@tonic-gate
3697c478bd9Sstevel@tonic-gate static UINT32
ec_gpe_handler(ACPI_HANDLE GpeDevice,UINT32 GpeNumber,void * ctx)370*26f3cdf0SGordon Ross ec_gpe_handler(ACPI_HANDLE GpeDevice, UINT32 GpeNumber, void *ctx)
3717c478bd9Sstevel@tonic-gate {
372*26f3cdf0SGordon Ross _NOTE(ARGUNUSED(GpeDevice))
373*26f3cdf0SGordon Ross _NOTE(ARGUNUSED(GpeNumber))
3747c478bd9Sstevel@tonic-gate _NOTE(ARGUNUSED(ctx))
3757c478bd9Sstevel@tonic-gate
376*26f3cdf0SGordon Ross /*
377*26f3cdf0SGordon Ross * With ec_ok==0, we will not install a GPE handler,
378*26f3cdf0SGordon Ross * so this is just paranoia. But if this were to
379*26f3cdf0SGordon Ross * happen somehow, don't add the taskq entry, and
380*26f3cdf0SGordon Ross * tell the caller we're done with this GPE call.
381*26f3cdf0SGordon Ross */
382*26f3cdf0SGordon Ross if (ec.ec_ok == 0)
383*26f3cdf0SGordon Ross return (ACPI_REENABLE_GPE);
384*26f3cdf0SGordon Ross
38527f7c583Smyers AcpiOsExecute(OSL_GPE_HANDLER, ec_gpe_callback, NULL);
386*26f3cdf0SGordon Ross
387*26f3cdf0SGordon Ross /*
388*26f3cdf0SGordon Ross * Returning zero tells the ACPI system that we will
389*26f3cdf0SGordon Ross * handle this event asynchronously.
390*26f3cdf0SGordon Ross */
3917c478bd9Sstevel@tonic-gate return (0);
3927c478bd9Sstevel@tonic-gate }
3937c478bd9Sstevel@tonic-gate
394450d6964Smyers /*
395*26f3cdf0SGordon Ross * Some systems describe the EC using an "ECDT" (table).
396*26f3cdf0SGordon Ross * If we find one use it (unless ec_ignore_ecdt is set).
397*26f3cdf0SGordon Ross * Modern systems don't provide an ECDT.
398450d6964Smyers */
399*26f3cdf0SGordon Ross static ACPI_STATUS
ec_probe_ecdt(void)400*26f3cdf0SGordon Ross ec_probe_ecdt(void)
401450d6964Smyers {
402*26f3cdf0SGordon Ross ACPI_TABLE_HEADER *th;
403*26f3cdf0SGordon Ross ACPI_TABLE_ECDT *ecdt;
404*26f3cdf0SGordon Ross ACPI_HANDLE dev_hdl;
405*26f3cdf0SGordon Ross ACPI_STATUS status;
406*26f3cdf0SGordon Ross
407*26f3cdf0SGordon Ross status = AcpiGetTable(ACPI_SIG_ECDT, 1, &th);
408*26f3cdf0SGordon Ross #ifndef DEBUG
409*26f3cdf0SGordon Ross if (status == AE_NOT_FOUND)
410*26f3cdf0SGordon Ross return (status);
411*26f3cdf0SGordon Ross #endif
412*26f3cdf0SGordon Ross if (ACPI_FAILURE(status)) {
413*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica: ECDT not found");
414*26f3cdf0SGordon Ross return (status);
415*26f3cdf0SGordon Ross }
416*26f3cdf0SGordon Ross if (ec_ignore_ecdt) {
417*26f3cdf0SGordon Ross /* pretend it was not found */
418*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica: ECDT ignored");
419*26f3cdf0SGordon Ross return (AE_NOT_FOUND);
420*26f3cdf0SGordon Ross }
421450d6964Smyers
422*26f3cdf0SGordon Ross ecdt = (ACPI_TABLE_ECDT *)th;
423*26f3cdf0SGordon Ross if (ecdt->Control.BitWidth != 8 ||
424*26f3cdf0SGordon Ross ecdt->Data.BitWidth != 8) {
425*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica: bad ECDT I/O width");
426*26f3cdf0SGordon Ross return (AE_BAD_VALUE);
427*26f3cdf0SGordon Ross }
428*26f3cdf0SGordon Ross status = AcpiGetHandle(NULL, (char *)ecdt->Id, &dev_hdl);
429*26f3cdf0SGordon Ross if (ACPI_FAILURE(status)) {
430*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica: no ECDT device handle");
431*26f3cdf0SGordon Ross return (status);
432450d6964Smyers }
433*26f3cdf0SGordon Ross
434*26f3cdf0SGordon Ross /*
435*26f3cdf0SGordon Ross * Success. Save info for attach.
436*26f3cdf0SGordon Ross */
437*26f3cdf0SGordon Ross ec.ec_base = ecdt->Data.Address;
438*26f3cdf0SGordon Ross ec.ec_sc = ecdt->Control.Address;
439*26f3cdf0SGordon Ross ec.ec_dev_hdl = dev_hdl;
440*26f3cdf0SGordon Ross ec.ec_gpe_hdl = NULL;
441*26f3cdf0SGordon Ross ec.ec_gpe_bit = ecdt->Gpe;
442*26f3cdf0SGordon Ross ec.ec_ok = 1;
443*26f3cdf0SGordon Ross
444*26f3cdf0SGordon Ross #ifdef DEBUG
445*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica:ec_probe_ecdt: success");
446*26f3cdf0SGordon Ross #endif
447450d6964Smyers return (0);
448450d6964Smyers }
449450d6964Smyers
450450d6964Smyers /*
451*26f3cdf0SGordon Ross * Called from AcpiWalkDevices() when an EC device is found
452450d6964Smyers */
453*26f3cdf0SGordon Ross static ACPI_STATUS
ec_find(ACPI_HANDLE obj,UINT32 nest,void * context,void ** rv)454*26f3cdf0SGordon Ross ec_find(ACPI_HANDLE obj, UINT32 nest, void *context, void **rv)
455450d6964Smyers {
456*26f3cdf0SGordon Ross _NOTE(ARGUNUSED(nest, rv))
457450d6964Smyers
458*26f3cdf0SGordon Ross *((ACPI_HANDLE *)context) = obj;
459*26f3cdf0SGordon Ross return (AE_OK);
460450d6964Smyers }
461450d6964Smyers
4627c478bd9Sstevel@tonic-gate /*
463*26f3cdf0SGordon Ross * Normal way to get the details about the EC,
464*26f3cdf0SGordon Ross * by searching the name space.
4657c478bd9Sstevel@tonic-gate */
4667c478bd9Sstevel@tonic-gate static ACPI_STATUS
ec_probe_ns(void)467*26f3cdf0SGordon Ross ec_probe_ns(void)
4687c478bd9Sstevel@tonic-gate {
469*26f3cdf0SGordon Ross ACPI_HANDLE dev_hdl;
4707c478bd9Sstevel@tonic-gate ACPI_BUFFER buf, crs;
471*26f3cdf0SGordon Ross ACPI_OBJECT *gpe_obj;
472*26f3cdf0SGordon Ross ACPI_HANDLE gpe_hdl;
473*26f3cdf0SGordon Ross ACPI_INTEGER gpe_bit;
474*26f3cdf0SGordon Ross ACPI_STATUS status;
475*26f3cdf0SGordon Ross int i, io_port_cnt;
476*26f3cdf0SGordon Ross uint16_t ec_sc, ec_base;
477*26f3cdf0SGordon Ross
478*26f3cdf0SGordon Ross dev_hdl = NULL;
479*26f3cdf0SGordon Ross (void) AcpiGetDevices("PNP0C09", &ec_find, (void *)&dev_hdl, NULL);
480*26f3cdf0SGordon Ross if (dev_hdl == NULL) {
481*26f3cdf0SGordon Ross #ifdef DEBUG
482*26f3cdf0SGordon Ross /* Not an error, just no EC on this machine. */
483*26f3cdf0SGordon Ross cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
484*26f3cdf0SGordon Ross "PNP0C09 not found");
485*26f3cdf0SGordon Ross #endif
486*26f3cdf0SGordon Ross return (AE_NOT_FOUND);
487*26f3cdf0SGordon Ross }
4887c478bd9Sstevel@tonic-gate
4897c478bd9Sstevel@tonic-gate /*
4907c478bd9Sstevel@tonic-gate * Find ec_base and ec_sc addresses
4917c478bd9Sstevel@tonic-gate */
4927c478bd9Sstevel@tonic-gate crs.Length = ACPI_ALLOCATE_BUFFER;
493*26f3cdf0SGordon Ross status = AcpiEvaluateObjectTyped(dev_hdl, "_CRS", NULL, &crs,
494*26f3cdf0SGordon Ross ACPI_TYPE_BUFFER);
495*26f3cdf0SGordon Ross if (ACPI_FAILURE(status)) {
496*26f3cdf0SGordon Ross cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
497*26f3cdf0SGordon Ross "_CRS object evaluate failed");
498*26f3cdf0SGordon Ross return (status);
4997c478bd9Sstevel@tonic-gate }
5007c478bd9Sstevel@tonic-gate
501db2bae30SDana Myers for (i = 0, io_port_cnt = 0;
502db2bae30SDana Myers i < ((ACPI_OBJECT *)crs.Pointer)->Buffer.Length; i++) {
5037c478bd9Sstevel@tonic-gate io_port_des_t *io_port;
5047c478bd9Sstevel@tonic-gate uint8_t *tmp;
5057c478bd9Sstevel@tonic-gate
506db2bae30SDana Myers tmp = ((ACPI_OBJECT *)crs.Pointer)->Buffer.Pointer + i;
5077c478bd9Sstevel@tonic-gate if (*tmp != IO_PORT_DES)
5087c478bd9Sstevel@tonic-gate continue;
5097c478bd9Sstevel@tonic-gate io_port = (io_port_des_t *)tmp;
5107c478bd9Sstevel@tonic-gate /*
511*26f3cdf0SGordon Ross * first port is ec_base and second is ec_sc
5127c478bd9Sstevel@tonic-gate */
513*26f3cdf0SGordon Ross if (io_port_cnt == 0)
514*26f3cdf0SGordon Ross ec_base = (io_port->min_base_hi << 8) |
5157c478bd9Sstevel@tonic-gate io_port->min_base_lo;
516*26f3cdf0SGordon Ross if (io_port_cnt == 1)
517*26f3cdf0SGordon Ross ec_sc = (io_port->min_base_hi << 8) |
5187c478bd9Sstevel@tonic-gate io_port->min_base_lo;
5197c478bd9Sstevel@tonic-gate
5207c478bd9Sstevel@tonic-gate io_port_cnt++;
5217c478bd9Sstevel@tonic-gate /*
5227c478bd9Sstevel@tonic-gate * Increment ahead to next struct.
5237c478bd9Sstevel@tonic-gate */
5247c478bd9Sstevel@tonic-gate i += 7;
5257c478bd9Sstevel@tonic-gate }
5267c478bd9Sstevel@tonic-gate AcpiOsFree(crs.Pointer);
527*26f3cdf0SGordon Ross if (io_port_cnt < 2) {
528*26f3cdf0SGordon Ross cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
529*26f3cdf0SGordon Ross "_CRS parse failed");
530*26f3cdf0SGordon Ross return (AE_BAD_VALUE);
531450d6964Smyers }
532450d6964Smyers
5337c478bd9Sstevel@tonic-gate /*
534*26f3cdf0SGordon Ross * Get the GPE info.
5357c478bd9Sstevel@tonic-gate */
5367c478bd9Sstevel@tonic-gate buf.Length = ACPI_ALLOCATE_BUFFER;
537*26f3cdf0SGordon Ross status = AcpiEvaluateObject(dev_hdl, "_GPE", NULL, &buf);
538*26f3cdf0SGordon Ross if (ACPI_FAILURE(status)) {
539*26f3cdf0SGordon Ross cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
540*26f3cdf0SGordon Ross "_GPE object evaluate");
541*26f3cdf0SGordon Ross return (status);
5427c478bd9Sstevel@tonic-gate }
543*26f3cdf0SGordon Ross gpe_obj = (ACPI_OBJECT *)buf.Pointer;
5447c478bd9Sstevel@tonic-gate /*
545*26f3cdf0SGordon Ross * process the GPE description
5467c478bd9Sstevel@tonic-gate */
547*26f3cdf0SGordon Ross switch (gpe_obj->Type) {
548*26f3cdf0SGordon Ross case ACPI_TYPE_INTEGER:
549*26f3cdf0SGordon Ross gpe_hdl = NULL;
550*26f3cdf0SGordon Ross gpe_bit = gpe_obj->Integer.Value;
551*26f3cdf0SGordon Ross break;
552*26f3cdf0SGordon Ross case ACPI_TYPE_PACKAGE:
553*26f3cdf0SGordon Ross if (gpe_obj->Package.Count != 2)
554*26f3cdf0SGordon Ross goto bad_gpe;
555*26f3cdf0SGordon Ross gpe_obj = gpe_obj->Package.Elements;
556*26f3cdf0SGordon Ross if (gpe_obj[1].Type != ACPI_TYPE_INTEGER)
557*26f3cdf0SGordon Ross goto bad_gpe;
558*26f3cdf0SGordon Ross gpe_hdl = gpe_obj[0].Reference.Handle;
559*26f3cdf0SGordon Ross gpe_bit = gpe_obj[1].Integer.Value;
560*26f3cdf0SGordon Ross break;
561*26f3cdf0SGordon Ross bad_gpe:
562*26f3cdf0SGordon Ross default:
563*26f3cdf0SGordon Ross status = AE_BAD_VALUE;
564*26f3cdf0SGordon Ross break;
565*26f3cdf0SGordon Ross }
566*26f3cdf0SGordon Ross AcpiOsFree(buf.Pointer);
567*26f3cdf0SGordon Ross if (ACPI_FAILURE(status)) {
568*26f3cdf0SGordon Ross cmn_err(CE_WARN, "!acpica:ec_probe_ns: "
569*26f3cdf0SGordon Ross "_GPE parse failed");
570*26f3cdf0SGordon Ross return (status);
571*26f3cdf0SGordon Ross }
5727c478bd9Sstevel@tonic-gate
573a3463f0aSDana Myers /*
574*26f3cdf0SGordon Ross * Success. Save info for attach.
575a3463f0aSDana Myers */
576*26f3cdf0SGordon Ross ec.ec_base = ec_base;
577*26f3cdf0SGordon Ross ec.ec_sc = ec_sc;
578*26f3cdf0SGordon Ross ec.ec_dev_hdl = dev_hdl;
579*26f3cdf0SGordon Ross ec.ec_gpe_hdl = gpe_hdl;
580*26f3cdf0SGordon Ross ec.ec_gpe_bit = gpe_bit;
581a3463f0aSDana Myers ec.ec_ok = 1;
582a3463f0aSDana Myers
583*26f3cdf0SGordon Ross #ifdef DEBUG
584*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica:ec_probe_ns: success");
585*26f3cdf0SGordon Ross #endif
586*26f3cdf0SGordon Ross return (0);
587*26f3cdf0SGordon Ross }
588*26f3cdf0SGordon Ross
589*26f3cdf0SGordon Ross /*
590*26f3cdf0SGordon Ross * Setup the Embedded Controller (EC) address space handler.
591*26f3cdf0SGordon Ross * Entered only if one of the EC probe methods found an EC.
592*26f3cdf0SGordon Ross */
593*26f3cdf0SGordon Ross static void
ec_init(void)594*26f3cdf0SGordon Ross ec_init(void)
595*26f3cdf0SGordon Ross {
596*26f3cdf0SGordon Ross ACPI_STATUS rc;
597*26f3cdf0SGordon Ross int x;
598*26f3cdf0SGordon Ross
599*26f3cdf0SGordon Ross /* paranoia */
600*26f3cdf0SGordon Ross if (ec.ec_ok == 0)
601*26f3cdf0SGordon Ross return;
602450d6964Smyers
603450d6964Smyers /*
604*26f3cdf0SGordon Ross * Drain the EC data register if something is left over from
605*26f3cdf0SGordon Ross * legacy mode
606a3463f0aSDana Myers */
607*26f3cdf0SGordon Ross if (inb(ec.ec_sc) & EC_OBF) {
608*26f3cdf0SGordon Ross x = inb(ec.ec_base); /* read and discard value */
609*26f3cdf0SGordon Ross #ifdef DEBUG
610*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!EC had something: 0x%x", x);
611*26f3cdf0SGordon Ross #endif
612*26f3cdf0SGordon Ross }
613a3463f0aSDana Myers
614a3463f0aSDana Myers /*
615*26f3cdf0SGordon Ross * Install an "EC address space" handler.
616*26f3cdf0SGordon Ross *
617*26f3cdf0SGordon Ross * This call does a name space walk under the passed
618*26f3cdf0SGordon Ross * object looking for child objects with an EC space
619*26f3cdf0SGordon Ross * region for which to install this handler. Using
620*26f3cdf0SGordon Ross * the ROOT object makes sure we find them all.
621*26f3cdf0SGordon Ross *
622*26f3cdf0SGordon Ross * XXX: Some systems return an error from this call
623*26f3cdf0SGordon Ross * after a partial success, i.e. where the NS walk
624*26f3cdf0SGordon Ross * installs on some nodes and fails on other nodes.
625*26f3cdf0SGordon Ross * In such cases, disabling the EC and GPE handlers
626*26f3cdf0SGordon Ross * makes things worse, so just report the error and
627*26f3cdf0SGordon Ross * leave the EC handler enabled.
628*26f3cdf0SGordon Ross *
629*26f3cdf0SGordon Ross * At one point, it seemed that doing this part of
630*26f3cdf0SGordon Ross * EC setup earlier may help, which is why this is
631*26f3cdf0SGordon Ross * now a separate function from ec_attach. Someone
632*26f3cdf0SGordon Ross * needs to figure our why some systems give us an
633*26f3cdf0SGordon Ross * error return from this call. (TODO)
634450d6964Smyers */
635*26f3cdf0SGordon Ross rc = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT,
636*26f3cdf0SGordon Ross ACPI_ADR_SPACE_EC, &ec_handler, NULL, NULL);
637*26f3cdf0SGordon Ross if (rc != AE_OK) {
638*26f3cdf0SGordon Ross cmn_err(CE_WARN, "!acpica:ec_init: "
639*26f3cdf0SGordon Ross "install AS handler, rc=0x%x", rc);
640*26f3cdf0SGordon Ross return;
6417c478bd9Sstevel@tonic-gate }
642*26f3cdf0SGordon Ross #ifdef DEBUG
643*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica:ec_init: success");
644*26f3cdf0SGordon Ross #endif
6457c478bd9Sstevel@tonic-gate }
6467c478bd9Sstevel@tonic-gate
647*26f3cdf0SGordon Ross /*
648*26f3cdf0SGordon Ross * Attach the EC General-Purpose Event (GPE) handler.
649*26f3cdf0SGordon Ross */
650*26f3cdf0SGordon Ross static void
ec_attach(void)651*26f3cdf0SGordon Ross ec_attach(void)
6527c478bd9Sstevel@tonic-gate {
653*26f3cdf0SGordon Ross ACPI_STATUS rc;
6547c478bd9Sstevel@tonic-gate
655*26f3cdf0SGordon Ross /*
656*26f3cdf0SGordon Ross * Guard against call without probe results.
657*26f3cdf0SGordon Ross */
658*26f3cdf0SGordon Ross if (ec.ec_ok == 0) {
659*26f3cdf0SGordon Ross cmn_err(CE_WARN, "!acpica:ec_attach: "
660*26f3cdf0SGordon Ross "no EC device found");
661*26f3cdf0SGordon Ross return;
662*26f3cdf0SGordon Ross }
6637c478bd9Sstevel@tonic-gate
664*26f3cdf0SGordon Ross /*
665*26f3cdf0SGordon Ross * Install the GPE handler and enable it.
666*26f3cdf0SGordon Ross */
667*26f3cdf0SGordon Ross rc = AcpiInstallGpeHandler(ec.ec_gpe_hdl, ec.ec_gpe_bit,
668*26f3cdf0SGordon Ross ACPI_GPE_EDGE_TRIGGERED, ec_gpe_handler, NULL);
669*26f3cdf0SGordon Ross if (rc != AE_OK) {
670*26f3cdf0SGordon Ross cmn_err(CE_WARN, "!acpica:ec_attach: "
671*26f3cdf0SGordon Ross "install GPE handler, rc=0x%x", rc);
672*26f3cdf0SGordon Ross goto errout;
673*26f3cdf0SGordon Ross }
6747c478bd9Sstevel@tonic-gate
675*26f3cdf0SGordon Ross rc = AcpiEnableGpe(ec.ec_gpe_hdl, ec.ec_gpe_bit);
676*26f3cdf0SGordon Ross if (rc != AE_OK) {
677*26f3cdf0SGordon Ross cmn_err(CE_WARN, "!acpica:ec_attach: "
678*26f3cdf0SGordon Ross "enable GPE handler, rc=0x%x", rc);
679*26f3cdf0SGordon Ross goto errout;
680*26f3cdf0SGordon Ross }
6817c478bd9Sstevel@tonic-gate
682*26f3cdf0SGordon Ross #ifdef DEBUG
683*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica:ec_attach: success");
684*26f3cdf0SGordon Ross #endif
685*26f3cdf0SGordon Ross return;
686*26f3cdf0SGordon Ross
687*26f3cdf0SGordon Ross errout:
688*26f3cdf0SGordon Ross AcpiRemoveGpeHandler(ec.ec_gpe_hdl, ec.ec_gpe_bit,
689*26f3cdf0SGordon Ross ec_gpe_handler);
6907c478bd9Sstevel@tonic-gate }
6917c478bd9Sstevel@tonic-gate
692*26f3cdf0SGordon Ross /*
693*26f3cdf0SGordon Ross * System Management Bus Controller (SMBC)
694*26f3cdf0SGordon Ross * These also go through the EC.
695*26f3cdf0SGordon Ross * (not yet supported)
696*26f3cdf0SGordon Ross */
6977c478bd9Sstevel@tonic-gate static void
smbus_attach(void)698*26f3cdf0SGordon Ross smbus_attach(void)
6997c478bd9Sstevel@tonic-gate {
700*26f3cdf0SGordon Ross #ifdef DEBUG
701*26f3cdf0SGordon Ross ACPI_HANDLE obj;
7027c478bd9Sstevel@tonic-gate
703*26f3cdf0SGordon Ross obj = NULL;
704*26f3cdf0SGordon Ross (void) AcpiGetDevices("ACPI0001", &ec_find, (void *)&obj, NULL);
705*26f3cdf0SGordon Ross if (obj != NULL) {
706*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica: found an SMBC Version 1.0");
7077c478bd9Sstevel@tonic-gate }
7087c478bd9Sstevel@tonic-gate
709*26f3cdf0SGordon Ross obj = NULL;
710*26f3cdf0SGordon Ross (void) AcpiGetDevices("ACPI0005", &ec_find, (void *)&obj, NULL);
711*26f3cdf0SGordon Ross if (obj != NULL) {
712*26f3cdf0SGordon Ross cmn_err(CE_NOTE, "!acpica: found an SMBC Version 2.0");
713*26f3cdf0SGordon Ross }
714*26f3cdf0SGordon Ross #endif /* DEBUG */
7157c478bd9Sstevel@tonic-gate }
7167c478bd9Sstevel@tonic-gate
717*26f3cdf0SGordon Ross /*
718*26f3cdf0SGordon Ross * Initialize the EC, if present.
719*26f3cdf0SGordon Ross */
7207c478bd9Sstevel@tonic-gate void
acpica_ec_init(void)7217c478bd9Sstevel@tonic-gate acpica_ec_init(void)
7227c478bd9Sstevel@tonic-gate {
723*26f3cdf0SGordon Ross ACPI_STATUS rc;
724*26f3cdf0SGordon Ross
7257c478bd9Sstevel@tonic-gate /*
726*26f3cdf0SGordon Ross * Initialize EC mutex here
7277c478bd9Sstevel@tonic-gate */
728*26f3cdf0SGordon Ross mutex_init(&ec.ec_mutex, NULL, MUTEX_DRIVER, NULL);
729a3463f0aSDana Myers
7307c478bd9Sstevel@tonic-gate /*
731*26f3cdf0SGordon Ross * First search the ACPI tables for an ECDT, and
732*26f3cdf0SGordon Ross * if not found, search the name space for it.
7337c478bd9Sstevel@tonic-gate */
734*26f3cdf0SGordon Ross rc = ec_probe_ecdt();
735*26f3cdf0SGordon Ross if (ACPI_FAILURE(rc))
736*26f3cdf0SGordon Ross rc = ec_probe_ns();
737*26f3cdf0SGordon Ross if (ACPI_SUCCESS(rc)) {
738*26f3cdf0SGordon Ross ec_init();
739*26f3cdf0SGordon Ross ec_attach();
740*26f3cdf0SGordon Ross }
741*26f3cdf0SGordon Ross smbus_attach();
7427c478bd9Sstevel@tonic-gate }
743