/* System dependent file for NCR System V Release 4 MP/RAS */
/* $Id: s-ncr.c,v 1.3 2004/05/29 18:21:29 jdd Exp $ */
#include "s.h"
#include <stdio.h>
#include <nlist.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/unistd.h>
#include <sys/mman.h>
#include <sys/utsname.h>
#include <sys/pic.h>
#include <sys/sysinfo.h>
#include <sys/mp/percpu.h>
#include <sys/immu.h>
#include <sys/scsi/cs.h>
#include <errno.h>
extern int diskflag, cpuflag;
extern char *kernelSymbols;
extern void draw_bar(int bar_num, int *states, int num_states);
extern void display_bars(int);
struct cpu_names
{
int cpu_type;
char cpu_type_name[8];
};
const struct cpu_names cpu_model_names[] =
{
{ CPU_386, "i386" },
{ CPU_486, "i486" },
#ifdef CPU_586
{ CPU_586, "Pentium" },
#elif defined CPU_Pentium
{ CPU_Pentium, "Pentium" },
#endif
};
/* The following defines are used to index the variable kernel_nl[] */
#define KERNEL_UTSNAME 0
#define KERNEL_PERCPUP 1
#define KERNEL_CPU_INFOS 2
#define KERNEL_NCPU 3
#define KERNEL_SCSI_STAT 4
#define KERNEL_LBOLT 5
/* kernel_nl[] -- list of symbol addresses needed.
*/
static struct nlist kernel_nl[] =
{
{ "utsname", 0 },
{ "percpup", 0 },
{ "cpu_infos", 0 },
{ "ncpu", 0 },
{ "SCSI_Stat_Ptr", 0 },
{ "lbolt", 0 },
{ 0, 0 }
};
/* kernel_symbols[] -- list of places to look for the running kernel's symbols
*/
char *kernel_symbols[] = {
0, /* used by -kernel option */
"/stand/unix.diag",
"/stand/unix",
"/stand/unix.old",
0
};
enum stype
{
CPU_STATE,
DISK_STATE,
};
enum stype state = CPU_STATE;
int kmem; /* file descriptor for /dev/kmem */
char *kernel_name; /* name of kernel (/stand/unix) */
struct utsname nodename; /* nodename.nodename */
int kmem_is_mapped;
int kmem_is_reading;
int kmem_can_map = 1;
/* Fake kernel memory mapping information
*/
typedef struct kmem_map
{
struct kmem_map *km_next;
void *km_addr;
size_t km_size;
char km_buf[4];
} kmem_map_t;
kmem_map_t *kmem_list; /* linked list of fake memory mappings */
/* CPU Information
*/
int kncpu; /* maximum number of cpu's on system */
int ncpus; /* number of cpu's which exist */
percpu_t **kpercpup; /* array of percpu_t pointers */
percpu_t **xpercpup; /* percpu_t pointers when mmapped */
percpu_t **mypercpup = 0; /* Pointers to each cpu's percpu area */
time_t *lastcpu; /* last snapshot of each cpu's times */
cpu_info_t *kcpu_infos; /* State of each cpu */
/* SCSI Disk Information
*/
int ndisks;
long kscsi_stat_ptr_addr;
long kaddr;
typedef struct diskinfo
{
dev_t di_dev;
SCSI_Device_t *di_kscsi;
StatStructure_t di_stats;
} diskinfo_t;
long *lboltp;
long last_lbolt, new_lbolt;
diskinfo_t *kscsi_stat;
SCSI_Device_t *lscsi_stat;
SCSI_Device_t tscsi_stat;
/* The size of the array which holds the cpu
execution information.
*/
#if defined( CPU_Pentium )
#define MPSYSINFO c_sysinfo
#else
#define MPSYSINFO c_mpinfo
#endif
#define CPU_STATE_SZ sizeof(mypercpup[0]->MPSYSINFO.cpu)
/* Computes the time spent in the specified state.
*/
#define delta(state) \
((int) mypercpup[cpu]->MPSYSINFO.cpu[state] - cputime[state])
/* Save the state times for a specific cpu
*/
#define savecpuinfo(cpu) \
memcpy(cputime, mypercpup[cpu]->MPSYSINFO.cpu, CPU_STATE_SZ);
/* The size of the local state time array
*/
#define NSTATES CPU_STATE_SZ
#define CPUSTATES (CPU_STATE_SZ / sizeof(mypercpup[0]->MPSYSINFO.cpu[0]))-1
/* Display an error message based upon errno and
return 0 to caller
*/
#define ERR_EXIT(msg) \
{ \
perror(msg); \
return 0; \
}
/* Read a specified number of bytes of kernel memory
and report any error conditions which might arise.
*/
#define seekreadok(desc, seekpos, buf, sz, msg) \
if (-1 == lseek(desc, seekpos, SEEK_SET)) \
ERR_EXIT("lseeking " msg); \
\
if ((sz) != read(desc, buf, (sz))) \
ERR_EXIT("reading " msg);
#define seekreaderr(desc, seekpos, buf, sz, msg) \
( -1 == lseek(desc, seekpos, SEEK_SET) || \
(sz) != read(desc, buf, (sz)))
/* fakethis() -- provide faked out memory mappings if unable to mmap kernel.
*/
void *
fakethis(void *loc, size_t sz)
{
kmem_map_t *mapping = (kmem_map_t *)malloc( sizeof(kmem_map_t) + sz );
if (!mapping)
return 0;
mapping->km_addr = loc;
mapping->km_size = sz;
mapping->km_next = kmem_list;
seekreadok(kmem, mapping->km_addr, mapping->km_buf, mapping->km_size,
"faking mmap");
if (!kmem_list && kmem_can_map)
fprintf(stderr, "Unable to mmap kernel addresses, using lseek/read\n");
kmem_list = mapping;
kmem_is_reading++;
return (void *) mapping->km_buf;
}
/* refreshfakes() -- Refresh fake mmap snapshots.
*/
int
refreshfakes(void)
{
kmem_map_t *mapping;
int errs = 0;
for (mapping = kmem_list; mapping; mapping = mapping->km_next)
if (seekreaderr(kmem, mapping->km_addr, mapping->km_buf, mapping->km_size,
"refreshing fake mmap's"))
errs++;
return errs;
}
/* Given an address (and size in bytes) in kernel virtual memory,
this function uses the mmap function to provide a mapping for
those addresses in the current process. The result is either
a pointer to the data in this processes virtual space or
(void *)-1 if unable to provide the mapping.
*/
void *
mapthis(int desc, void *locp, unsigned long sz)
{
unsigned long pgoffs = (unsigned long) locp & POFFMASK;
unsigned long pgstrt = (unsigned long) locp & PG_ADDR;
unsigned long pgend = ((unsigned long) locp + sz) & PG_ADDR;
unsigned long mapsz = pgend - pgstrt + NBPP;
char *map = (char *)-1;
if (kmem_can_map)
map = mmap(0, mapsz, PROT_READ, MAP_SHARED, desc, pgstrt);
if ((char *)-1 == map)
{
if (kmem_is_mapped)
fprintf(stderr, "Mixing mapping and lseek/read kernel memory?\n");
map = fakethis(locp, sz);
return map ? map : (void *)-1;
}
kmem_is_mapped++;
return (void *) (map + pgoffs);
}
/* get_kernel_symbols -- Find the file containing the symbol definitions
for the currently running kernel.
The kernel must be running out of /stand (on an svr4 system) and the
symbol value for utsname must match the name returned by the uname()
system call.
This function is called given a list of possible files from /stand which
may be running and returns the name of the first file which appears
to be correct.
*/
char *
get_kernel_symbols(int kmemfd, char **knamelist)
{
char **cpp;
int nf, errs;
int nsz = strlen(nodename.nodename) + 1;
struct utsname nname;
if (1 >= nsz)
fprintf(stderr, "xcpustate: Empty node name\n");
if (kernelSymbols)
*knamelist = kernelSymbols;
if (!*knamelist) knamelist++; /* skip over initial NULL pointer */
for (cpp = knamelist; *cpp; cpp++)
{
for (nf = 0; kernel_nl[nf].n_name; nf++)
kernel_nl[nf].n_value = 0;
nf = nlist(*cpp, kernel_nl);
if (-1 == nf || 0 == kernel_nl[KERNEL_UTSNAME].n_value)
continue;
if (!seekreaderr(kmemfd, kernel_nl[KERNEL_UTSNAME].n_value,
&nname.nodename, nsz, "utsname") &&
0 == strncmp(nodename.nodename, nname.nodename, nsz))
{
for (errs = nf = 0; kernel_nl[nf].n_name; nf ++)
if (0 == kernel_nl[nf].n_value)
{
fprintf(stderr, "kernel symbol `%s' not found\n",
kernel_nl[nf].n_name);
errs++;
}
if (errs)
return 0;
return *cpp;
}
}
fprintf(stderr, "xcpustate: No kernel symbol file matches running kernel\n");
return 0;
}
/* init_cpu_init() -- Initialize for CPU display
*/
int
init_cpu_info(void)
{
int nf;
seekreadok(kmem,
kernel_nl[KERNEL_NCPU].n_value,
&kncpu,
sizeof(kncpu),
"/dev/kmem [ncpu]");
if (kncpu > 64 || kncpu < 0)
{
fprintf(stderr, "Unlikely value for ncpu found.\n");
return 0;
}
kcpu_infos = mapthis(kmem,
(void *)kernel_nl[KERNEL_CPU_INFOS].n_value,
kncpu*sizeof(cpu_info_t));
if (-1 == (int)kcpu_infos)
ERR_EXIT("mapping cpu_infos[]");
kpercpup = mapthis(kmem,
(void *)kernel_nl[KERNEL_PERCPUP].n_value,
kncpu * sizeof(percpu_t *));
if (-1 == (int)kpercpup)
ERR_EXIT("mapping percpup[]");
mypercpup = (percpu_t **) malloc( sizeof(percpu_t *) * kncpu );
if (!mypercpup)
ERR_EXIT("allocating memory");
xpercpup = (percpu_t **) malloc( kncpu * sizeof(percpu_t *));
if (!kpercpup)
ERR_EXIT("allocating memory");
memset(xpercpup, 0, kncpu * sizeof(percpu_t *));
for (ncpus = nf = 0; nf < kncpu; nf++)
if (kcpu_infos[nf].cpu_flag & CPU_EXISTS)
{
if (kpercpup[nf])
{
xpercpup[ncpus] = kpercpup[nf];
mypercpup[ncpus] = mapthis(kmem,
xpercpup[ncpus],
sizeof(*(kpercpup[nf])));
if (-1 == mypercpup[ncpus])
ERR_EXIT("mapping percpu area");
}
ncpus++;
}
nf = ncpus * CPU_STATE_SZ;
lastcpu = (time_t *) malloc(nf);
memset(lastcpu, 0, nf);
return ncpus;
}
/* scsitype() -- return string naming scsi device type.
*/
char *
scsitype(int t)
{
switch (t)
{
case SCSI_DAD_TYPE: return "disk";
case SCSI_SAD_TYPE: return "tape";
case SCSI_PRT_TYPE: return "printer";
case SCSI_PROC_TYPE: return "proc";
case SCSI_WORM_TYPE: return "worm";
case SCSI_CD_ROM_TYPE: return "cdrom";
case SCSI_SCAN_TYPE: return "scanner";
case SCSI_OPTICAL_TYPE: return "optical";
case SCSI_CHANGER_TYPE: return "changer";
case SCSI_COMM_TYPE: return "comm";
}
return "<unknown>";
}
/* diskname() -- Return string naming disk device.
*/
char *
diskname(Partition_Identifier_t *s)
{
static char name[52];
if (0 == s->IO_Bus)
{
if (0 == s->SCSI_Controller)
sprintf(name, "c%xt%xd%xs%x",
s->SCSI_Bus,
s->PUN, s->LUN, s->Partition);
else
sprintf(name, "c%x%xt%xd%xs%x",
s->SCSI_Controller, s->SCSI_Bus,
s->PUN, s->LUN, s->Partition);
}
else
sprintf(name, "c%x%x%xt%xd%xs%x",
s->IO_Bus, s->SCSI_Controller, s->SCSI_Bus,
s->PUN, s->LUN, s->Partition);
return name;
}
int
init_disk_info(void)
{
long kaddr;
int cdisk;
int head = 0, nprocs = 0, ncdroms = 0, ntapes = 0;
seekreadok(kmem,
kernel_nl[KERNEL_SCSI_STAT].n_value,
&kscsi_stat_ptr_addr,
sizeof(kscsi_stat_ptr_addr),
"/dev/kmem [SCSI_Stat_Ptr]");
kaddr = kscsi_stat_ptr_addr;
ndisks = 0;
do
{
seekreadok(kmem,kaddr,&tscsi_stat,sizeof(tscsi_stat),"/dev/kmem [SCSI]");
switch (tscsi_stat.inquiry_data.Periph_Device_Type)
{
case SCSI_DAD_TYPE: ndisks++; break;
case SCSI_SAD_TYPE: ntapes++; break;
case SCSI_CD_ROM_TYPE: ncdroms++; break;
case SCSI_PROC_TYPE: nprocs++; break;
}
kaddr = (long) tscsi_stat.Statistics.ForwardPointer;
}
while (kscsi_stat_ptr_addr != kaddr);
fprintf(stderr, "SCSI: %d disks, %d tapes, %d cdroms, %d controllers\n",
ndisks, ntapes, ncdroms, nprocs);
kscsi_stat = (diskinfo_t *) malloc((1+ndisks) * sizeof(diskinfo_t));
if (!kscsi_stat)
ERR_EXIT("Allocate SCSI Disk memory");
#if 0
lscsi_stat = (SCSI_Device_t *) malloc((1+ndisks) * sizeof(SCSI_Device_t));
if (!lscsi_stat)
ERR_EXIT("Allocate SCSI Statistics array");
memset(lscsi_stat, 0, ndisks * sizeof(SCSI_Device_t));
#endif
kaddr = kscsi_stat_ptr_addr;
cdisk = 0;
lboltp = (long *) mapthis(kmem, (void *)kernel_nl[KERNEL_LBOLT].n_value,
sizeof(long));
if (-1 == (long) lboltp)
{
perror("Can't map lbolt");
return 0;
}
last_lbolt = *lboltp;
do
{
lscsi_stat = (SCSI_Device_t *) mapthis(kmem, (void *)kaddr,
sizeof(tscsi_stat));
if (-1 == (long) lscsi_stat)
{
perror("Can't map SCSI information");
return 0;
}
kscsi_stat[cdisk].di_kscsi = (SCSI_Device_t *) lscsi_stat;
kscsi_stat[cdisk].di_dev = lscsi_stat->dev;
memcpy(&kscsi_stat[cdisk].di_stats, &lscsi_stat->Statistics,
sizeof(lscsi_stat->Statistics));
if (!head++)
fprintf(stderr," # Bus Ctrlr SBus PUN LUN Part dev_num Name\n");
fprintf(stderr, "%3d %3d %5d %4d %3d %3d %4d %08x %s (%s)\n",
cdisk,
lscsi_stat->SCSI_device.IO_Bus,
lscsi_stat->SCSI_device.SCSI_Controller,
lscsi_stat->SCSI_device.SCSI_Bus,
lscsi_stat->SCSI_device.PUN,
lscsi_stat->SCSI_device.LUN,
lscsi_stat->SCSI_device.Partition,
lscsi_stat->dev,
diskname(&lscsi_stat->SCSI_device),
scsitype(lscsi_stat->inquiry_data.Periph_Device_Type));
kaddr = (long) lscsi_stat->Statistics.ForwardPointer;
if (SCSI_DAD_TYPE == lscsi_stat->inquiry_data.Periph_Device_Type)
cdisk++;
}
while (kscsi_stat_ptr_addr != kaddr);
if (cdisk != ndisks)
ERR_EXIT("Error counting disks");
return ndisks;
}
/* Called by -version */
void
version()
{
rintf("NCR SVR4 MP/RAS\n");
}
/* Called at the beginning to inquire how many bars are needed
*/
int
num_bars(void)
{
int nf;
int errs;
int sz;
char *pgs = 0;
kmem = open("/dev/kmem", O_RDONLY);
if (-1 == kmem)
ERR_EXIT("opening /dev/kmem");
if (-1 == uname(&nodename))
ERR_EXIT("uname");
kernel_name = get_kernel_symbols(kmem, kernel_symbols);
if (!kernel_name)
return 0;
if (diskflag)
{
if (cpuflag)
fprintf(stderr, "Only disk or cpu state can be monitored\n");
state = DISK_STATE;
return init_disk_info();
}
return init_cpu_info();
}
/*
* Indicates how many levels each bar has. For most machines, each bar will
* have the same stuff. But one can, for instance, display memory use on one
* bar, processor levels on others, etc.
*/
void
bar_items(nbars, items)
int nbars;
int items[]; /* nbars items in this */
{
int i;
int nlevels = (DISK_STATE == state) ? 2 : CPUSTATES;
for (i = 0; i < nbars; )
items[i++] = nlevels;
}
/* Called after num_bars to ask for bar names */
char **
label_cpu_bars(nbars)
{
char **names;
int hsz = strlen(nodename.nodename);
/***** ' ' '#' %d ' ' 'i486' ' ' *****/
int rowsz = hsz + 1 + 1 + 3 + 1 + 7 + 2;
int sz = nbars * rowsz;
int cpu, i;
char *cp;
shorten(nodename.nodename);
names = (char **)malloc(sz + sizeof(char *) * nbars);
cp = (char *)names + sizeof(char *) * nbars;
for (cpu = i = 0; i < kncpu; i++)
if (kcpu_infos[i].cpu_flag & CPU_EXISTS)
{
names[cpu++] = cp;
if (1 == ncpus)
sprintf(cp, "%s", nodename.nodename);
else if (0 == i)
sprintf(cp, "%s #%d", nodename.nodename, i);
else
sprintf(cp, "%*s #%d", hsz, "", i);
cp += rowsz;
}
return names;
}
char **
label_disk_bars(nbars)
{
char **names;
char *cp;
int i;
int hsz = sizeof("cxxxtxdxsx");
names = (char **) malloc( hsz * nbars + nbars * sizeof(char *) );
cp = (char *) names + sizeof(char *) * nbars;
for (i = 0; i < nbars; i++)
{
names[i] = cp;
lscsi_stat = kscsi_stat[i].di_kscsi;
strcpy(cp, diskname(&lscsi_stat->SCSI_device));
cp += hsz;
}
return names;
}
char **
label_bars(nbars)
{
switch (state)
{
case CPU_STATE: return label_cpu_bars(nbars);
case DISK_STATE: return label_disk_bars(nbars);
}
return NULL;
}
/* Called after the bars are created to do machine dependent inits */
void
init_bars(nbars)
int nbars;
{
(void) display_bars(nbars);
}
/*
This function is called every interval to compute and display
the bars. It should call draw_bar() with the bar number, the array of
integer values to display in the bar, and the number of values in the
array.
*/
void
display_cpu_bars(nbars)
{
int states[NSTATES];
int nstates;
int cpu, i;
time_t *cputime = lastcpu;
for (i = cpu = 0; i < kncpu && cpu < nbars; i++)
{
if (0 == (kcpu_infos[i].cpu_flag & CPU_EXISTS))
continue;
nstates = 0;
if (kcpu_infos[i].cpu_flag & CPU_RUNNING && kpercpup[i])
{
if (kpercpup[cpu] != xpercpup[i])
{ /* mapping changed or became available */
xpercpup[cpu] = kpercpup[i];
mypercpup[ i] = mapthis(kmem, xpercpup[cpu],
sizeof(*(xpercpup[cpu])));
}
states[nstates++] = delta(CPU_IDLE);
states[nstates++] = delta(CPU_USER);
states[nstates++] = delta(CPU_KERNEL);
states[nstates++] = delta(CPU_WAIT) + delta(CPU_SXBRK);
#if 0
states[nstates++] = delta(CPU_SXBRK);
#endif
}
else
{
states[nstates++] = 100; /* CPU_IDLE */
states[nstates++] = 0; /* CPU_USER */
states[nstates++] = 0; /* CPU_KERNEL */
states[nstates++] = 0; /* CPU_WAIT */
#if 0
states[nstates++] = 0; /* CPU_SXBRK */
#endif
}
savecpuinfo(cpu);
draw_bar(cpu, states, nstates);
cpu++;
cputime += CPU_STATE_SZ / sizeof(time_t);
}
}
void
display_disk_bars(nbars)
{
int states[2];
int i;
time_t t;
static time_t maxt = 0;
static int npr = 0;
time_t dlt;
struct iotime *new, *old;
new_lbolt = *lboltp;
dlt = new_lbolt - last_lbolt;
last_lbolt = new_lbolt;
for (i = 0; i < ndisks; i++)
{
lscsi_stat = kscsi_stat[i].di_kscsi;
if (kscsi_stat[i].di_dev != lscsi_stat->dev)
{ /* device changed */
states[0] = 100; /* idle */
states[1] = 0; /* busy */
}
else
{
new = &lscsi_stat->Statistics.SCSI_Stats;
old = &kscsi_stat[i].di_stats.SCSI_Stats;
t = new->io_act - old->io_act;
states[1] = t;
if (states[1] > dlt)
states[0] = 0;
else
states[0] = dlt - states[1];
old->io_act = new->io_act;
old->io_resp = new->io_resp;
}
draw_bar(i, states, 2);
}
}
void
display_bars(nbars)
{
if (kmem_list)
refreshfakes();
switch (state)
{
case CPU_STATE: display_cpu_bars(nbars);
case DISK_STATE: display_disk_bars(nbars);
}
return;
}
syntax highlighted by Code2HTML, v. 0.9.1