Coherent4.2.10/coh.386/lib/cmn_err.c
/* $Header: $ */
#define _DDI_DKI 1
#define _SYSV4 1
/*
* Error-reporting code, in the DDI/DKI mould.
*
* $Log: $
*/
/*
*-IMPORTS:
* <common/ccompat.h>
* __PROTO ()
* __USE_PROTO__
* __ARGS ()
* <common/xdebug.h>
* __LOCAL__
* <sys/inline.h>
* splhi ()
* splx ()
* <sys/ksynch.h>
* lock_t
* LOCK ()
* LOCK_ALLOC ()
* UNLOCK ()
* <limits.h>
* CHAR_BIT
* <stdarg.h>
* va_list
* va_start ()
* va_arg ()
* va_end ()
*
*-PRIVATE DEPENDENCIES:
* _put_console ()
* _put_putbuf ()
*/
#include <common/ccompat.h>
#include <common/xdebug.h>
#include <sys/inline.h>
#include <sys/ksynch.h>
#include <sys/ddi.h>
#include <sys/types.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <sys/cmn_err.h>
#ifdef __MSDOS__
# include <process.h>
#endif
/*
*-GLOBAL VARIABLES:
* conlock lock to single-thread console output
* putlock lock to single-thread putbuf output
*/
/*
* Note that these locks are always acquired in the order conlock -> putlock;
* this is required to ensure that the system remains deadlock-free. The
* hierarchy mechanism is used to enforce this constraint.
*
* We use the user namespace for these since if they are actually made public
* by definitions in <common/xdebug.h> then they will still not conflict with
* user symbols (ie, we assume that this module is an "implementation"
* module...
*/
__LOCAL__ lock_t * _conlock;
__LOCAL__ lock_t * _putlock;
/*
* The following are not global variables, they're static constants.
*/
static char * message_level [] = {
"",
"NOTICE : ",
"WARNING : ",
"PANIC : ",
"<INVALID ERROR LEVEL> : "
};
/*
* The following external function declarations exist for internal layering
* purposes and do not appear in any headers. The names begin with a single
* underscore (reserved for the user with internal linkage, reserved for the
* system with external linkage) so as not to conflict with any user-defined
* symbol names.
*
* If you find this use of the implementation namespace distasteful, then
* you'll have to move the function definitions in here.
*
* Note also that these functions will have the default language linkage
* for the translator, ie we allow these functions to be mangled C++. This
* constrains them to be produced by the same translator used for this
* module.
*/
#if __COHERENT__
void putchar __PROTO ((int _c));
#define _put_console(c) putchar (c)
#else
void _put_console __PROTO ((unsigned char _outch));
#endif
void _put_putbuf __PROTO ((unsigned char _outch));
/*
*-STATUS:
* DDI/DKI
*
*-NAME:
* cmn_err () display an error message or panic the system
*
*-SYNOPSIS:
* #include <sys/cmn_err.h>
*
* void cmn_err (int level, char * format, ...);
*
*-ARGUMENTS:
* level "level" indicates the severity of the error condition.
* Valid levels are:
* CE_CONT used to continue a previous message or to
* display an informative message not connected
* with an error.
* CE_NOTE used to display a message preceded with
* NOTICE:. This message is used to report system
* events that do not necessarily require action,
* by may interest the system administrator. For
* example, a message saying that a sector on a
* disk needs to be accessed repeatedly before it
* can be accessed correctly might be noteworthy.
* CE_WARN use to display a message preceded with
* WARNING:. This message is used to report
* system events that require immediate
* attention, such as those where if an action is
* not taken, the system may panic. For example,
* where a peripheral device does not initialize
* correctly, this level should be used.
* CE_PANIC used to display a message preceded by PANIC:,
* and panic the system. Drivers should use this
* level only for debugging or in the case of
* severe errors that indicate that the system
* cannot continue to function. This level halts
* processing.
*
* format The message to be displayed. By default, the message
* is sent both to the system console and to the circular
* kernel buffer, "putbuf". If the first character in
* "format" is an exclamation point ("!"), the message
* goes only to "putbuf". If the first character in
* "format" is a circumflex ("^"), the message goes only
* to the console. The size of the circular buffer
* "putbuf" is defined by the kernel variable "putbufsz".
* Driver developers or administrators can read the
* "putbuf" buffer using appropriate debugging or
* administrative tools.
*
* cmn_err () appends '\n' to each "format" string, even
* when a message is sent to "putbuf", except when level
* is CE_CONT.
*
* Valid conversion specifications are %s, %u, %d, %o,
* and %x. The cmn_err () function is otherwise similar
* to the printf () library subroutine is its
* interpretation of the "format" string, except that
* cmn_err () does not accept length specifications in
* conversion specifications. For example, %3d is invalid
* and will be treated as a literal string, resulting in
* a mismatch of arguments.
*
* ... the set of arguments passed with the message being
* displayed. Any argument within the range of supported
* conversion specifications can be passed.
*
*-DESCRIPTION:
* cmn_err () displays a specified message on the console and/or stores
* it in the kernel buffer "putbuf". cmn_err () can also panic the
* system.
*
* At times a driver may encounter error conditions requiring the
* attention of a system console monitor. These conditions may mean
* halting the system; however, this must be done with caution. Except
* during the debugging stage, or in the case of a serious, unrecoverable
* error, a driver should never stop the system.
*
* The cmn_err () function with the CE_CONT argument can be used by
* driver developers as a driver code debugging tool. However, using
* cmn_err () in this capacity can change system timing characteristics.
*
*-NOTES:
* Does not sleep.
*
* If "level" is CE_PANIC, then driver defined basic locks, read/write
* locks, and sleep locks may not be held across calls to this function.
* For other levels, locks may be held.
*/
enum { OUT_CONSOLE = 1,
OUT_PUTBUF = 2,
OUT_LONG = 4,
OUT_SHORT = 8,
OUT_SIGNED = 16
};
typedef char * _string_t; /* for va_arg () */
typedef unsigned short _ushort_t;
typedef unsigned int _uint_t;
typedef unsigned long _ulong_t;
#if __USE_PROTO__
void (cmn_err) (int level, char * format, ...)
#else
void
cmn_err __ARGS ((level, format))
int level;
char * format;
#endif
{
/*
* The following local data is used for converting numeric data to
* strings. Numbuf is defined such that it is large enough to hold
* the maximum number of digits that could be generated from any
* conversion specification (currently, the longest possible spec is
* %lo).
*/
char numbuf [(sizeof (long) * CHAR_BIT + 2) / 3 + 1];
char * numptr;
int radix;
long svalue;
unsigned long value;
va_list args;
unsigned output; /* who to output to */
pl_t con_pl; /* for locking */
pl_t put_pl;
if (format == NULL)
format = "<cmn_err: no error message was supplied>";
if (* format == '!') {
format ++;
output = OUT_PUTBUF;
} else if (* format == '^') {
format ++;
output = OUT_CONSOLE;
} else
output = OUT_PUTBUF | OUT_CONSOLE;
/*
* Before we generate any output, we should lock the console/putbuf
* so that our messages won't get interspersed with diagnostic output
* from interrupt handlers or other CPUs.
*
* If we get called before the locks have been allocated, proceed
* at the highest interrupt priority as a fallback.
*/
if (_conlock != NULL) {
if ((output & OUT_CONSOLE) != 0)
con_pl = LOCK (_conlock, plhi);
if ((output & OUT_PUTBUF) != 0)
put_pl = LOCK (_putlock, plhi);
} else
con_pl = splhi ();
/*
* Check that the level argument is reasonable.
*/
if (level < CE_CONT || level > CE_PANIC)
level = CE_INVALID;
/*
* We should think about generating the date and as accurate a time
* reading as possible along with the fixed-text header.
*
* For now, we'll just generate some fixed text.
*/
numptr = message_level [level];
while (* numptr != 0) {
if ((output & OUT_CONSOLE) != 0)
_put_console (* numptr);
if ((output & OUT_PUTBUF) != 0)
_put_putbuf (* numptr);
numptr ++;
}
/*
* Now start generating formatted output.
*
* The manual page quoted in the function header doesn't say whether
* the 'l' and 'h' modifiers are available for conversion
* specifications. We'll implement them anyway.
*/
va_start (args, format);
while (* format != 0) {
if (format [0] == '%') {
/*
* Process the conversion-specifications.
*
* We temporarily borrow 'radix' as a temporary for
* working out how long the conversion specification
* is.
*/
output &= ~ (OUT_LONG | OUT_SHORT | OUT_SIGNED);
radix = 2;
if (format [radix - 1] == 'h') {
output |= OUT_SHORT;
radix ++;
} else if (format [radix - 1] == 'l') {
output |= OUT_LONG;
radix ++;
}
switch ((format += radix) [-1]) {
case 's':
numptr = va_arg (args, _string_t);
goto out_string;
case 'd':
/*
* This is the only signed numeric conversion
* specification, so we process the sign here.
*/
radix = 10;
if ((output & OUT_SHORT) != 0)
svalue = (short) va_arg (args, int);
else if ((output & OUT_LONG) != 0)
svalue = va_arg (args, long);
else
svalue = va_arg (args, int);
if (svalue < 0) {
if ((output & OUT_CONSOLE) != 0)
_put_console ('-');
if ((output & OUT_PUTBUF) != 0)
_put_putbuf ('-');
svalue = - svalue;
}
output |= OUT_SIGNED;
break;
case 'o':
radix = 8;
break;
case 'u':
radix = 10;
break;
case 'x':
radix = 16;
break;
case '%':
/*
* Special case: %% -> %
*/
format -= 1;
goto bad_format;
default:
/*
* It's not a valid conversion, so we back
* up "format".
*/
format -= radix;
goto bad_format;
}
/*
* All numeric conversions come here.
*/
if ((output & OUT_SIGNED) != 0)
value = svalue;
else if ((output & OUT_SHORT) != 0)
value = (_ushort_t) va_arg (args, _uint_t);
else if ((output & OUT_LONG) != 0)
value = va_arg (args, _ulong_t);
else
value = va_arg (args, _uint_t);
/*
* Now we generate the output digits one-at-a-time in
* reverse order. We step back from the end of the
* conversion buffer so that at the end of the
* conversion process we have a C string with the
* characters in a 'normal' order.
*
* (This allows the final output loop to be folded in
* with the string-output code).
*/
numptr = & numbuf [sizeof (numbuf) - 1];
* numptr = 0; /* terminate the output string */
do {
int rem = (int) (value % radix);
value /= radix;
* -- numptr = (rem < 10) ? rem + '0' :
rem + 'a' - 10;
} while (value > 0);
/*
* Copy temporary numeric-conversion string or
* user-supplied string argument to the output.
*/
out_string:
while (* numptr != 0) {
if ((output & OUT_CONSOLE) != 0)
_put_console (* numptr);
if ((output & OUT_PUTBUF) != 0)
_put_putbuf (* numptr);
numptr ++;
}
continue;
}
bad_format:
if ((output & OUT_CONSOLE) != 0)
_put_console (* format);
if ((output & OUT_PUTBUF) != 0)
_put_putbuf (* format);
format ++;
}
/*
* Output a trailing '\n' unless the level is CE_CONT
*/
if (level != CE_CONT) {
if ((output & OUT_CONSOLE) != 0)
_put_console ('\n');
if ((output & OUT_PUTBUF) != 0)
_put_putbuf ('\n');
}
/*
* Now release the locks we acquired, and consider returning to the
* caller (unless this is a panic).
*
* A minor point: if we panic, we should release our locks but
* without causing interrupts to be enabled on this CPU; in order to
* do this we kludge "con_pl" and "put_pl" to be "plhi";
*/
if (level >= CE_PANIC) /* panic on an invalid level */
con_pl = put_pl = plhi;
if (_conlock != NULL) {
if ((output & OUT_PUTBUF) != 0)
UNLOCK (_putlock, put_pl);
if ((output & OUT_CONSOLE) != 0)
LOCK (_conlock, con_pl);
} else
(void) splx (con_pl);
if (level >= CE_PANIC) {
/*
* This CPU is shutting down. The actions to be taken here
* are so highly system-specific that we'll just throw them
* into another file.
*/
#ifdef __MSDOS__
splx (plbase);
exit (3); /* call atexit () functions */
#else
extern void backtrace ();
backtrace ();
for (;;)
/* DO NOTHING */ ;
#endif
}
}