Subject: NetBSD/mips cooking book
To: None <port-mips@netbsd.org>
From: Toru Nishimura <nisimura@itc.aist-nara.ac.jp>
List: port-mips
Date: 04/01/2000 16:23:56
Now here is another hack before going to bed I made a few nights ago,
to describe how to port NetBSD/mips to your MIPS box.  The following
would be rather titled like as "Forget the PeeCee (it's wasting), your
MIPS serve you smarter."

--
                  ..... NetBSD/mips cooking book .....

NetBSD internal is designed for the ease of porting new target
hardware as possible.  There is a well defined set of frameworks
and building block routines to absorb and reflect the system
peculiarities varying across target hardwares.

void
cpu_intr(status, cause, pc, ipending)
	u_int32_t status;	SR - status register
	u_int32_t cause;	CAUSE - cause register
	u_int32_t pc;		EPC - pc upon exception
	u_int32_t ipending;	CAUSE & SR - servicable interrupt(s)
{
	if (ipending & MIPS_INT_MASK_0) {
		servicing for INT #0
		...
		cause &= ~MIPS_INT_MASK_0; /* to reenable */
	}
	if (ipending & MIPS_INT_MASK_1) {
		servicing for INT #1
		...
		cause &= ~MIPS_INT_MASK_1; /* to reenable */
	}
	reenable interrupts completing device service logics
	_setspl(MIPS_SR_INT_IE | (status & ~cause & MIPS_HARD_INT_MASK));

	if (ipending & MIPS_SOFT_INT_MASK_1) {
		    || ((ssir != 0) && (status & MIPS_SOFT_INT_MASK_1))) {
		servicing for protocol stack by software simulated interrupt

		unsigned n;
		n = ssir; ssir = 0;
		_clrsoftintr(MIPS_SOFT_INT_MASK_1);
		DO_SIR(SIR_NET, netintr());	 
	}
	if (ipending & MIPS_SOFT_INT_MASK_0) {
		servicing for software clock machinary

		_clrsoftintr(MIPS_SOFT_INT_MASK_0);
                uvmexp.softs++;
                intrcnt[SOFTCLOCK_INTR]++;
                softclock();
	}
}

void
TARGETinit()
{
	- determine the platform type if system internals differ each other
	- detect MIPS processor CPU personality
	- detect additional system peculiarities of configuration
	- wiredown exception vector of MIPS processor
	- flush cache entirely
	- analyze boot flag and booting/root device to use
	- prepare serial/video console to make sure printf() will work
	- determine the maximum size of physical memory
	- pay close attention to analyze non-contiguous memory layout
	- having memory layout detail by ROM callback would be helpful
	- uvm_page_physload()
	- mips_init_msgbuf()
	- grab proc0 USPACE with pmap_steal_memory(), then have it initialized
	- allocsys() for various system use
	- pmap_bootstrap()
}

void
cpu_startup()
{
	- almost identical across various NetBSD/mips ports.
	- target peculiaries should be inside TARGETinit() described above
}

void
cpu_reboot(howto, bootstr)
	volatile int howto;	/* XXX volatile to keep gcc happy */
	char *bootstr;
{
		...
		resettodr();
	}

	splhigh();

	/* If rebooting and a dump is requested do it. */
	if ((howto & (RB_DUMP | RB_HALT)) == RB_DUMP)
		dumpsys();
haltsys:
	/* run any shutdown hooks */
	doshutdownhooks();
	/* dive into ROM console CLI, restart the system or poweroff */
	ROMrestart(howto);
}

If target port has incompatible internal designs and it's hard to handle
them with a single logic, have a set of routines peculiar to each model in
parallel.

In a file TARGET/include/sysconf.h, also useful as <machine/sysconf.h>;

struct platform {
	(*iointr)(u_int32_t, u_int32_t, u_int32_t, u_int32_t);
	(*machinit)(void);
	(*consinit)(void);
	(*clkread)(void);
	(*boo)(...);
	(*foo)(...);
	(*woo)(...);
};
extern struct platform platform;

#define	TARGET_MODEL_A 0
#define	TARGET_MODEL_B 1
...

If device assignment of MIPS_INT_MASK and servicing logics differ
largely to each other;

void
cpu_intr(status, cause, pc, ipending)
{
	(*platform.iointr)(status, cause, pc, ipending);

	if ((ipending & MIPS_SOFT_INT_MASK_1)
		    || ((ssir != 0) && (status & MIPS_SOFT_INT_MASK_1))) {
		unsigned n;
		n = ssir; ssir = 0;
		_clrsoftintr(MIPS_SOFT_INT_MASK_1);
		DO_SIR(SIR_NET, netintr());	 

		_clrsoftintr(MIPS_SOFT_INT_MASK_1);
	}
	if (ipending & MIPS_SOFT_INT_MASK_0) {
		_clrsoftintr(MIPS_SOFT_INT_MASK_0);
                uvmexp.softs++;
                intrcnt[SOFTCLOCK_INTR]++;
                softclock();
	}
}

If model dependent parameters and hardware initialization logics differ
largely to each other, put the following call in TARGETinit().

	(*platform.machinit)();

The body would do like as;

	- pick and set various parameters peculiar to this model
	- initialize hardwares to make them sane state

If console configuration and initialization logic differ largely to
each other, put the following call in TARGETinit().

	(*platform.consinit)();

Keep in mind that malloc() is not available because target
system is not fully initialized at the moment and there is no help
from interrupt driven processing.  It'd be ok to glob a region of
physical memory stealing some from available memory.

If it was acceptable to defer console initialization for printf() until
when NetBSD's MI initialization logic calls consinit(), then have the
following instead of empty consinit().

void
consinit()
{
	(*plantform.consinit)();
}

Availability of ROM printf() would be a great plus for swifty
development cycle to proceed.  It's ok to utilize ROM calls for
putc(c) and getc() implemented in (possibly costy) I/O polling as
follows;

struct consdev promcd = {
	NULL,		/* probe */
	NULL,		/* init */
	ROMgetc,	/* getc */
	ROMputc,	/* putc */
	nullcnpollc,	/* pollc */
	makedev(0, 0),
	CN_DEAD,
};

Then, have cons = &promcd; inside TARGETinit(). ROM printf() would work
well before full scale initialization either for serial console or
video console.

If hardclock() interplation logic for finer precision differs
largely to each other;

void
microtime(tvp)
	struct timeval *tvp;
{
	*tvp = time;
	/* to interpolate HZ to obtain finer precision */
	tvp->tv_usec += (*platform.clkread)();
	if (tvp->tv_usec >= 1000000) {
		...
	}
}

MIPS3 or later generation of MIPS processor has onchip cycle
counter inside and its value is available at any circumstances by
reading COUNT register.  That's the most easy way to interpolate HZ.

	*tvp = time; /* ticked by hardclock() */
	__asm __volatile ("mfc0 %0, $9" :: "=r" (cp0r9));
	/* adjust the value with least instructions */
	tvp->tv_usec += LIGHT_WEIGHT_INTERPORATION(cp0r9);
	...

Feeding hardclock() source, a.ka. programmable clockwork

MIPS3 or later generation of MIPS processor has onchip cycle
counter inside and can be configured it as clock source by setting
an appropriate value in COMPARE register.  When the COUNT register
value reachs to the value of COMPARE register, INT #5 is posted asking
for clock service.  If the cycle counter is used for hardclock() source,
cpu_intr() would have the following section;

	if (ipending & MIPS_INT_MASK_5) {
		__asm __volatile ("mfc0 %0, $9" :: "=r"(cycles));
		cycles += TICK_CYCLES;
		__asm __volatile ("mtc0 %0, $11; nop; nop" : "r"(cycles));

		cf.sr = status;
		cf.pc = pc;
		hardclock(&cf);

		cause &= ~MIPS_INT_MASK_5;
		/* CAUSE was already cleared by mtc0 above, though */
	}

Historically hardclock() source has been fed by a crystal oscillator or
a timekeeper clock source with calendar NVRAM. They are not herribly
useful to have finer wall clock time resolution.

Some of companion ASIC, like RAMBO ASIC in Pizzaz (R3000 Magnum) or ARC
chipset (R4000 Magnum) made by MIPS Computer System, can feed
hardclock() source by programming registers similar to MIPS3 onchip
circuit.  Other companion ASIC, like IOASIC found in DECstation, can
provide free-running cycle counter register as a handy measurement tool
for performance analysis.  In either case, they would be useful to
interpolate HZ for finer precision wallclock time.

--