xref: /titanic_44/usr/src/uts/i86pc/io/tzmon/tzmon.c (revision b369f4b871a39ef94e220443957975f445f52eb6)
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 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Solaris x86 ACPI ThermalZone Monitor
29  */
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 #include <sys/errno.h>
34 #include <sys/conf.h>
35 #include <sys/modctl.h>
36 #include <sys/open.h>
37 #include <sys/stat.h>
38 #include <sys/ddi.h>
39 #include <sys/sunddi.h>
40 #include <sys/ksynch.h>
41 #include <sys/uadmin.h>
42 #include <sys/acpi/acpi.h>
43 #include <sys/acpica.h>
44 #include <sys/sdt.h>
45 
46 #include "tzmon.h"
47 
48 
49 #define	TZMON_ENUM_TRIP_POINTS	1
50 #define	TZMON_ENUM_DEV_LISTS	2
51 #define	TZMON_ENUM_ALL		(TZMON_ENUM_TRIP_POINTS	| TZMON_ENUM_DEV_LISTS)
52 
53 
54 /* cb_ops or dev_ops forward declarations */
55 static	int	tzmon_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd,
56     void *arg, void **result);
57 static	int	tzmon_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
58 static	int	tzmon_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
59 
60 /* other forward declarations */
61 static void tzmon_notify_zone(ACPI_HANDLE obj, UINT32 val, void *ctx);
62 static void tzmon_eval_int(ACPI_HANDLE obj, char *method, int *rv);
63 static thermal_zone_t *tzmon_alloc_zone();
64 static void tzmon_free_zone_list();
65 static void tzmon_discard_buffers(thermal_zone_t *tzp);
66 static void tzmon_enumerate_zone(ACPI_HANDLE obj, thermal_zone_t *tzp,
67 	int enum_flag);
68 static ACPI_STATUS tzmon_zone_callback(ACPI_HANDLE obj, UINT32 nest,
69     void *ctx, void **rv);
70 static void tzmon_find_zones(void);
71 static void tzmon_monitor(void *ctx);
72 static void tzmon_set_power_device(ACPI_HANDLE dev, int on_off);
73 static void tzmon_set_power(ACPI_BUFFER devlist, int on_off);
74 static void tzmon_eval_zone(thermal_zone_t *tzp);
75 static void tzmon_do_shutdown(void);
76 
77 extern void halt(char *);
78 
79 static struct cb_ops	tzmon_cb_ops = {
80 	nodev,			/* no open routine	*/
81 	nodev,			/* no close routine	*/
82 	nodev,			/* not a block driver	*/
83 	nodev,			/* no print routine	*/
84 	nodev,			/* no dump routine	*/
85 	nodev,			/* no read routine	*/
86 	nodev,			/* no write routine	*/
87 	nodev,			/* no ioctl routine	*/
88 	nodev,			/* no devmap routine	*/
89 	nodev,			/* no mmap routine	*/
90 	nodev,			/* no segmap routine	*/
91 	nochpoll,		/* no chpoll routine	*/
92 	ddi_prop_op,
93 	0,			/* not a STREAMS driver	*/
94 	D_NEW | D_MP,		/* safe for multi-thread/multi-processor */
95 };
96 
97 static struct dev_ops tzmon_ops = {
98 	DEVO_REV,		/* devo_rev */
99 	0,			/* devo_refcnt */
100 	tzmon_getinfo,		/* devo_getinfo */
101 	nulldev,		/* devo_identify */
102 	nulldev,		/* devo_probe */
103 	tzmon_attach,		/* devo_attach */
104 	tzmon_detach,		/* devo_detach */
105 	nodev,			/* devo_reset */
106 	&tzmon_cb_ops,		/* devo_cb_ops */
107 	(struct bus_ops *)0,	/* devo_bus_ops */
108 	NULL,			/* devo_power */
109 };
110 
111 extern	struct	mod_ops mod_driverops;
112 
113 static	struct modldrv modldrv = {
114 	&mod_driverops,
115 	"ACPI Thermal Zone Monitor %I%",
116 	&tzmon_ops,
117 };
118 
119 static	struct modlinkage modlinkage = {
120 	MODREV_1,		/* MODREV_1 indicated by manual */
121 	(void *)&modldrv,
122 	NULL,			/* termination of list of linkage structures */
123 };
124 
125 /* globals for this module */
126 static dev_info_t	*tzmon_dip;
127 static thermal_zone_t	*zone_list;
128 static int		zone_count;
129 static kmutex_t		zone_list_lock;
130 static kcondvar_t	zone_list_condvar;
131 
132 
133 /*
134  * _init, _info, and _fini support loading and unloading the driver.
135  */
136 int
137 _init(void)
138 {
139 	return (mod_install(&modlinkage));
140 }
141 
142 
143 int
144 _info(struct modinfo *modinfop)
145 {
146 	return (mod_info(&modlinkage, modinfop));
147 }
148 
149 
150 int
151 _fini(void)
152 {
153 	return (mod_remove(&modlinkage));
154 }
155 
156 
157 static int
158 tzmon_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
159 {
160 	if (cmd != DDI_ATTACH)
161 		return (DDI_FAILURE);
162 
163 	if (tzmon_dip != NULL)
164 		return (DDI_FAILURE);
165 
166 	/*
167 	 * Check to see if ACPI CA services are available
168 	 */
169 	if (AcpiSubsystemStatus() != AE_OK)
170 		return (DDI_FAILURE);
171 
172 	mutex_init(&zone_list_lock, NULL, MUTEX_DRIVER, NULL);
173 	cv_init(&zone_list_condvar, NULL, CV_DRIVER, NULL);
174 
175 	tzmon_find_zones();
176 	mutex_enter(&zone_list_lock);
177 	if (zone_count < 1) {
178 		mutex_exit(&zone_list_lock);
179 		mutex_destroy(&zone_list_lock);
180 		cv_destroy(&zone_list_condvar);
181 		return (DDI_FAILURE);
182 	}
183 	mutex_exit(&zone_list_lock);
184 
185 	if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR, 0,
186 	    DDI_PSEUDO, 0) == DDI_FAILURE) {
187 		tzmon_free_zone_list();
188 		mutex_destroy(&zone_list_lock);
189 		cv_destroy(&zone_list_condvar);
190 		return (DDI_FAILURE);
191 	}
192 
193 	tzmon_dip = dip;
194 
195 	ddi_report_dev(dip);
196 
197 	return (DDI_SUCCESS);
198 }
199 
200 
201 /*ARGSUSED*/
202 static int
203 tzmon_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
204 {
205 	int error;
206 
207 	switch (infocmd) {
208 	case DDI_INFO_DEVT2DEVINFO:
209 		*result = tzmon_dip;
210 		if (tzmon_dip == NULL)
211 			error = DDI_FAILURE;
212 		else
213 			error = DDI_SUCCESS;
214 		break;
215 	case DDI_INFO_DEVT2INSTANCE:
216 		*result = 0;
217 		error = DDI_SUCCESS;
218 		break;
219 	default:
220 		*result = NULL;
221 		error = DDI_FAILURE;
222 	}
223 
224 	return (error);
225 }
226 
227 
228 static int
229 tzmon_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
230 {
231 	if (cmd != DDI_DETACH)
232 		return (DDI_FAILURE);
233 
234 	/* discard zone list assets */
235 	tzmon_free_zone_list();
236 
237 	ddi_remove_minor_node(dip, NULL);
238 	tzmon_dip = NULL;
239 
240 	mutex_destroy(&zone_list_lock);
241 	cv_destroy(&zone_list_condvar);
242 
243 	return (DDI_SUCCESS);
244 }
245 
246 
247 /*
248  * tzmon_notify_zone
249  * Thermal zone notification handler.
250  */
251 static void
252 tzmon_notify_zone(ACPI_HANDLE obj, UINT32 val, void *ctx)
253 {
254 	thermal_zone_t *tzp = (thermal_zone_t *)ctx;
255 
256 	switch (val) {
257 	case 0x80:	/* Thermal Zone status changed */
258 		tzmon_eval_zone(tzp);
259 		break;
260 	case 0x81:	/* Thermal Zone trip points changed */
261 		tzmon_enumerate_zone(obj, tzp, TZMON_ENUM_TRIP_POINTS);
262 		break;
263 	case 0x82:	/* Device Lists changed */
264 		tzmon_enumerate_zone(obj, tzp, TZMON_ENUM_DEV_LISTS);
265 		break;
266 	case 0x83:	/* Thermal Relationship Table changed */
267 		/* not handling _TRT objects, so not handling this event */
268 		cmn_err(CE_CONT, "?tzmon: thermal relationship table changed");
269 		break;
270 	default:
271 		break;
272 	}
273 }
274 
275 
276 /*
277  * tzmon_eval_int
278  * Evaluate the object/method as an integer.
279  */
280 static void
281 tzmon_eval_int(ACPI_HANDLE obj, char *method, int *rv)
282 {
283 
284 	if (acpica_eval_int(obj, method, rv) != AE_OK)
285 		*rv = -1;
286 }
287 
288 
289 /*
290  * tzmon_alloc_zone
291  * Allocate memory for the zone structure and initialize it lock mutex.
292  */
293 static thermal_zone_t *
294 tzmon_alloc_zone()
295 {
296 	thermal_zone_t *tzp;
297 
298 	tzp = kmem_zalloc(sizeof (thermal_zone_t), KM_SLEEP);
299 	mutex_init(&tzp->lock, NULL, MUTEX_DRIVER, NULL);
300 
301 	return (tzp);
302 }
303 
304 
305 /*
306  * tzmon_free_zone_list
307  * Free the zone list, either because attach failed or detach initiated.
308  */
309 static void
310 tzmon_free_zone_list()
311 {
312 	thermal_zone_t *tzp = zone_list;
313 
314 	while (tzp != NULL) {
315 		thermal_zone_t *next;
316 
317 		mutex_enter(&tzp->lock);
318 
319 		/*
320 		 * Remove the notify handler for the zone.  Not much to
321 		 * do if this fails (since we are on our way out), so
322 		 * just ignore failure.
323 		 */
324 		(void) AcpiRemoveNotifyHandler(tzp->obj, ACPI_DEVICE_NOTIFY,
325 		    tzmon_notify_zone);
326 
327 		/* Shut down monitor thread, if running */
328 		if (tzp->taskq != NULL) {
329 			tzp->polling_period = 0;
330 			cv_broadcast(&zone_list_condvar);
331 
332 			/* Drop mutex to allow the thread to run */
333 			mutex_exit(&tzp->lock);
334 			ddi_taskq_destroy(tzp->taskq);
335 			mutex_enter(&tzp->lock);
336 		}
337 
338 		tzmon_discard_buffers(tzp);
339 		mutex_exit(&tzp->lock);
340 		mutex_destroy(&tzp->lock);
341 
342 		next = tzp->next;
343 		kmem_free(tzp, sizeof (thermal_zone_t));
344 		tzp = next;
345 	}
346 }
347 
348 
349 static void
350 tzmon_discard_buffers(thermal_zone_t *tzp)
351 {
352 	int level;
353 
354 	for (level = 0; level < TZ_NUM_LEVELS; level++) {
355 		if (tzp->al[level].Pointer != NULL)
356 			AcpiOsFree(tzp->al[level].Pointer);
357 	}
358 
359 	if (tzp->psl.Pointer != NULL)
360 		AcpiOsFree(tzp->psl.Pointer);
361 }
362 
363 
364 /*
365  * tzmon_enumerate_zone
366  * Enumerates the contents of a thermal zone and updates passed-in
367  * thermal_zone or creates a new one if tzp is NULL. Newly-created
368  * zones are linked into the global zone_list.
369  */
370 static void
371 tzmon_enumerate_zone(ACPI_HANDLE obj, thermal_zone_t *tzp, int enum_flag)
372 {
373 	ACPI_STATUS status;
374 	int	level;
375 	char	abuf[5];
376 
377 	/*
378 	 * Newly-created zones and existing zones both require
379 	 * some individual attention.
380 	 */
381 	if (tzp == NULL) {
382 		/* New zone required */
383 		tzp = tzmon_alloc_zone();
384 		mutex_enter(&zone_list_lock);
385 		tzp->next = zone_list;
386 		zone_list = tzp;
387 		zone_count++;
388 		mutex_exit(&zone_list_lock);
389 		mutex_enter(&tzp->lock);
390 		tzp->obj = obj;
391 
392 		/*
393 		 * Set to a low level.  Will get set to the actual
394 		 * current power level when the thread monitor polls
395 		 * the current temperature.
396 		 */
397 		tzp->current_level = 0;
398 
399 		status = AcpiInstallNotifyHandler(obj, ACPI_DEVICE_NOTIFY,
400 		    tzmon_notify_zone, (void *)tzp);
401 		ASSERT(status == AE_OK);
402 	} else {
403 		/* Existing zone - toss out allocated items */
404 		mutex_enter(&tzp->lock);
405 		ASSERT(tzp->obj == obj);
406 
407 		if (enum_flag & TZMON_ENUM_DEV_LISTS)
408 			tzmon_discard_buffers(tzp);
409 	}
410 
411 	if (enum_flag & TZMON_ENUM_TRIP_POINTS) {
412 		for (level = 0; level < TZ_NUM_LEVELS; level++) {
413 			(void) snprintf(abuf, 5, "_AC%d", level);
414 			tzmon_eval_int(obj, abuf, &tzp->ac[level]);
415 
416 		}
417 
418 		tzmon_eval_int(obj, "_CRT", &tzp->crt);
419 		tzmon_eval_int(obj, "_HOT", &tzp->hot);
420 		tzmon_eval_int(obj, "_PSV", &tzp->psv);
421 	}
422 
423 	if (enum_flag & TZMON_ENUM_DEV_LISTS) {
424 		for (level = 0; level < TZ_NUM_LEVELS; level++) {
425 			if (tzp->ac[level] == -1) {
426 				tzp->al[level].Length = 0;
427 				tzp->al[level].Pointer = NULL;
428 			} else {
429 				(void) snprintf(abuf, 5, "_AL%d", level);
430 				tzp->al[level].Length = ACPI_ALLOCATE_BUFFER;
431 				tzp->al[level].Pointer = NULL;
432 				if (AcpiEvaluateObject(obj, abuf, NULL,
433 				    &tzp->al[level]) != AE_OK) {
434 					cmn_err(CE_WARN, "tzmon: error "
435 					    "evaluating _AL object");
436 
437 					tzp->al[level].Length = 0;
438 					tzp->al[level].Pointer = NULL;
439 				}
440 			}
441 		}
442 
443 		tzp->psl.Length = ACPI_ALLOCATE_BUFFER;
444 		tzp->psl.Pointer = NULL;
445 		(void) AcpiEvaluateObject(obj, "_PSL", NULL, &tzp->psl);
446 	}
447 
448 	tzmon_eval_int(obj, "_TC1", &tzp->tc1);
449 	tzmon_eval_int(obj, "_TC2", &tzp->tc2);
450 	tzmon_eval_int(obj, "_TSP", &tzp->tsp);
451 	tzmon_eval_int(obj, "_TZP", &tzp->tzp);
452 
453 	if (tzp->tzp == 0) {
454 		tzp->polling_period = 0;
455 	} else {
456 		if (tzp->tzp < 0)
457 			tzp->polling_period = TZ_DEFAULT_PERIOD;
458 		else
459 			tzp->polling_period = tzp->tzp/10;
460 
461 		/* start monitor thread if needed */
462 		if (tzp->taskq == NULL) {
463 			tzp->taskq = ddi_taskq_create(tzmon_dip,
464 			    "AcpiThermalMonitor", 1, TASKQ_DEFAULTPRI, 0);
465 			if (tzp->taskq == NULL) {
466 				tzp->polling_period = 0;
467 				cmn_err(CE_WARN, "tzmon: could not create"
468 				    " monitor thread - monitor by notify only");
469 			} else {
470 				(void) ddi_taskq_dispatch(tzp->taskq,
471 				    tzmon_monitor, tzp, DDI_SLEEP);
472 			}
473 		}
474 	}
475 
476 	mutex_exit(&tzp->lock);
477 }
478 
479 
480 /*
481  * tzmon_zone_callback
482  * Enumerate the thermal zone if it has a _TMP (current thermal zone
483  * operating temperature) method.
484  */
485 /*ARGSUSED*/
486 static ACPI_STATUS
487 tzmon_zone_callback(ACPI_HANDLE obj, UINT32 nest, void *ctx, void **rv)
488 {
489 	ACPI_HANDLE tmpobj;
490 
491 	/*
492 	 * We get both ThermalZone() and Scope(\_TZ) objects here;
493 	 * look for _TMP (without which a zone is invalid) to pick
494 	 * between them (and ignore invalid zones)
495 	 */
496 	if (AcpiGetHandle(obj, "_TMP", &tmpobj) == AE_OK) {
497 		tzmon_enumerate_zone(obj, NULL, TZMON_ENUM_ALL);
498 	}
499 
500 	return (AE_OK);
501 }
502 
503 
504 /*
505  * tzmon_find_zones
506  * Find all of the thermal zones by calling a ACPICA function that
507  * walks the ACPI namespace and invokes a callback for each thermal
508  * object found.
509  */
510 static void
511 tzmon_find_zones()
512 {
513 	ACPI_STATUS status;
514 	int retval;
515 
516 	status = AcpiWalkNamespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT,
517 	    8, tzmon_zone_callback, NULL, (void **)&retval);
518 
519 	ASSERT(status == AE_OK);
520 }
521 
522 
523 /*
524  * tzmon_monitor
525  * Run as a separate thread, this wakes according to polling period and
526  * checks particular objects in the thermal zone.  One instance per
527  * thermal zone.
528  */
529 static void
530 tzmon_monitor(void *ctx)
531 {
532 	thermal_zone_t *tzp = (thermal_zone_t *)ctx;
533 	clock_t ticks;
534 
535 	do {
536 		/* Check out the zone */
537 		tzmon_eval_zone(tzp);
538 
539 		/* Go back to sleep */
540 		mutex_enter(&tzp->lock);
541 		ticks = drv_usectohz(tzp->polling_period * 1000000);
542 		if (ticks > 0)
543 			(void) cv_timedwait(&zone_list_condvar, &tzp->lock,
544 			    ddi_get_lbolt() + ticks);
545 		mutex_exit(&tzp->lock);
546 	} while (ticks > 0);
547 }
548 
549 
550 /*
551  * tzmon_set_power_device
552  */
553 static void
554 tzmon_set_power_device(ACPI_HANDLE dev, int on_off)
555 {
556 	ACPI_BUFFER rb;
557 	ACPI_OBJECT *pr0;
558 	ACPI_STATUS status;
559 	int i;
560 
561 	rb.Length = ACPI_ALLOCATE_BUFFER;
562 	rb.Pointer = NULL;
563 	status = AcpiEvaluateObject(dev, "_PR0", NULL, &rb);
564 	if (status != AE_OK) {
565 		cmn_err(CE_NOTE, "tzmon: can not set power");
566 		return;
567 	}
568 
569 	pr0 = ((ACPI_OBJECT *)rb.Pointer);
570 	if (pr0->Type != ACPI_TYPE_PACKAGE) {
571 		cmn_err(CE_NOTE, "tzmon: can not set power");
572 		AcpiOsFree(rb.Pointer);
573 		return;
574 	}
575 
576 	for (i = 0; i < pr0->Package.Count; i++) {
577 		status = AcpiEvaluateObject(
578 		    pr0->Package.Elements[i].Reference.Handle,
579 		    on_off ? "_ON" : "_OFF", NULL, NULL);
580 		if (status != AE_OK) {
581 			cmn_err(CE_WARN, "tz_set_pwr_dev: failed to set %d\n",
582 			    i);
583 		}
584 	}
585 
586 	AcpiOsFree(rb.Pointer);
587 }
588 
589 
590 /*
591  * tzmon_set_power
592  * Turn on or turn off all devices in the supplied list.
593  */
594 static void
595 tzmon_set_power(ACPI_BUFFER devlist, int on_off)
596 {
597 	ACPI_OBJECT *devs;
598 	int i;
599 
600 	devs = ((ACPI_OBJECT *)devlist.Pointer);
601 	if (devs->Type != ACPI_TYPE_PACKAGE) {
602 		cmn_err(CE_NOTE, "tzmon: can not set power");
603 		return;
604 	}
605 
606 	for (i = 0; i < devs->Package.Count; i++)
607 		tzmon_set_power_device(
608 		    devs->Package.Elements[i].Reference.Handle, on_off);
609 }
610 
611 
612 /*
613  * tzmon_eval_zone
614  * Evaluate the current conditions within the thermal zone.
615  */
616 static void
617 tzmon_eval_zone(thermal_zone_t *tzp)
618 {
619 	int tmp, new_level, level;
620 
621 	mutex_enter(&tzp->lock);
622 
623 	/* get the current temperature from ACPI */
624 	tzmon_eval_int(tzp->obj, "_TMP", &tmp);
625 	DTRACE_PROBE1(tzmon_tz_temp, int, tmp);
626 
627 	/* _HOT handling */
628 	if (tzp->hot > 0 && tmp >= tzp->hot) {
629 		cmn_err(CE_WARN, "tzmon: temp %d is above _HOT (%d)\n",
630 		    tmp, tzp->hot);
631 
632 		tzmon_do_shutdown();
633 	}
634 
635 	/* _CRT handling */
636 	if (tzp->crt > 0 && tmp >= tzp->crt) {
637 		cmn_err(CE_WARN, "tzmon: temp %d is above _CRT (%d)\n",
638 		    tmp, tzp->crt);
639 
640 		/* shut down (fairly) immediately */
641 		mdboot(A_REBOOT, AD_HALT, NULL, B_FALSE);
642 	}
643 
644 	/*
645 	 * use the temperature to determine whether the thermal zone
646 	 * is at a new active cooling threshold level
647 	 */
648 	for (level = 0, new_level = -1; level < TZ_NUM_LEVELS; level++) {
649 		if (tzp->ac[level] >= 0 && (tmp >= tzp->ac[level])) {
650 			new_level = level;
651 			break;
652 		}
653 	}
654 
655 	/*
656 	 * if the active cooling threshold has changed, turn off the
657 	 * devices associated with the old one and turn on the new one
658 	 */
659 	if (tzp->current_level != new_level) {
660 		if ((tzp->current_level >= 0) &&
661 		    (tzp->al[tzp->current_level].Length != 0))
662 			tzmon_set_power(tzp->al[tzp->current_level], 0);
663 
664 		if ((new_level >= 0) &&
665 		    (tzp->al[new_level].Length != 0))
666 			tzmon_set_power(tzp->al[new_level], 1);
667 
668 		tzp->current_level = new_level;
669 	}
670 
671 	mutex_exit(&tzp->lock);
672 }
673 
674 
675 /*
676  * tzmon_do_shutdown
677  * Initiates shutdown by sending a SIGPWR signal to init.
678  */
679 static void
680 tzmon_do_shutdown(void)
681 {
682 	proc_t *initpp;
683 
684 	mutex_enter(&pidlock);
685 	initpp = prfind(P_INITPID);
686 	mutex_exit(&pidlock);
687 
688 	/* if we can't find init, just halt */
689 	if (initpp == NULL) {
690 		mdboot(A_REBOOT, AD_HALT, NULL, B_FALSE);
691 	}
692 
693 	/* graceful shutdown with inittab and all getting involved */
694 	psignal(initpp, SIGPWR);
695 }
696