Porting OpenBSD to the Solbourne S4000

[ Index ] [ Prev: El jardín de senderos que se bifurcan ]

Work Around The Clock

Before I could tackle the ethernet interface, I had something much more important to get: a clock. Without a clock, no process scheduling, no device timeouts, no life in the kernel.

From what I could gather about the S4000, the iCU chip provides a 100Hz clock, wired to interrupt 10, just where the BSD kernel expects its scheduling clock. Apparently, simply enabling interrupts on the iCU triggers it.

Unfortunately for me, I don't know which magic I have been frobbing which causes the scheduling clock to be disabled, but I could never get it to interrupt. Experimenting with this took me a couple days...

Then, I tried with the profiling interrupt on level 14, but at some point, it would always stop interrupting as well. Tired of hunting for a bug in my code, I gave up on it as well. But I was still needing a clock. And I had ``lost'' one more day.

On the kbus machines, Gingold had apparently met a similar problem; he eventually hijacked the clock of one of the serial chips. For now, I decided to follow the same route and using the keyboard/mouse chip as a clock only. I will have to find something else by the time I get the keyboard working, but since I only aim for serial console for now, this is not a major problem.

For some reason, the original zsclock code in the OpenBSD/kbus port would not enable interrupts. I don't know how it could ever work, maybe the interrupt was enabled in some hidden place in locore... This was easy to fix, and I quickly started to receive periodic interrupts on interrupt level 12.

Wait! Level 12. The serial level.

But the kernel expects the clock on level 10, and I did not want to change this (in order to share intr.c with sparc, and because I still hope to get the iCU clock working at some point). What could I do to redirect this interrupt to level 10?

Being a lazy person, the easiest way was to have the hard interrupt at level 12 trigger a soft interrupt at level 10.

Since there are no soft interrupts at higher levels, the delay in processing clock interrupts would be basically zero, unless there were a lot of activity on the serial ports (which would still delay the clock as well if it was to directly interrupt on level 10). The zsclock interrupt handler became:

zsclock_stint(struct zs_chanstate *cs, int force)
	u_char rr0;

	rr0 = zs_read_csr(cs);
	cs->cs_rr0 = rr0;

	 * Retrigger the interrupt as a soft interrupt, because we need
	 * a trap frame for hardclock().

	zs_write_csr(cs, ZSWR0_RESET_STATUS);

This would almost work, except that I now needed soft interrupt management code! On sparc (well, sun4c), the ienab_bis and ienab_bic routines take care of this (bis stands for bit set, while bic stands for bit clear).

On sparc, these routines usually amount to frobbing bits in a specific registers. But on the solbourne, all I have is the low 4 bits of the iCU interrupt register, which tell which software interrupt to trigger. The logic then needs the kernel to remember the bitmask of the enabled soft interrupts, and to take care of programming the correct value for the 4 bit field. It turned out I did not need (yet) to provide ienab_bic, and ienab_bis was pretty straightforward:

 * Soft interrupt handling

int	kap_sir;

ienab_bis(int bis)
	int s;
	int mask = 1 << (bis - 1);
	u_int32_t icr;

	s = splhigh();
	if (kap_sir < mask) {
		 * We become the most important bit in kap_sir. Reprogram
		 * the GLU_ICR soft interrupt dispatcher.
		icr = lda(GLU_ICR, ASI_PHYS_IO) >> 24;
		icr = (icr & ~GICR_DISPATCH_MASK) | bis;
		sta(GLU_ICR, ASI_PHYS_IO, icr << 24);
	kap_sir |= mask;

Things, however, went a bit more hairy when servicing the actual interrupts, since this requires the serviced bit to be cleared. On sun4c, this would amount to this:

	sethi	%hi(INTRREG_VA), %l6
	ldub	[%l6 + %lo(INTRREG_VA)], %l5
	andn	%l5, %l4, %l5
	stb	%l5, [%l6 + %lo(INTRREG_VA)]

i.e. simply masking a bit in a register. The equivalent code for the solbourne became more convoluted:
	sethi	%hi(_C_LABEL(kap_sir)), %l6
	mov	1, %l5
	sll	%l5, %l4, %l5				! l5 = 1 << (intno - 1)
	ld	[%l6 + %lo(_C_LABEL(kap_sir))], %l4
	andn	%l4, %l5, %l4				! clear bit
	/* skip loop if kap_sir becomes zero */
	clr	%l5					! intnum = 0;
	cmp	%l4, 0
	be	2f
	 st	%l4, [%l6 + %lo(_C_LABEL(kap_sir))]
	/* now find out the topmost bit set in kap_sir */
1:	srl	%l4, 1, %l4				! while (sir >>= 1)
	cmp	%l4, 0
	bne	1b
	 add	%l5, 1, %l5				!	intnum++;
2:	set	GLU_ICR, %l6
	lda	[%l6] ASI_PHYS_IO, %l4			! read icr
	srl	%l4, 24, %l4
	andn	%l4, GICR_DISPATCH_MASK, %l4		! clear softintr bits
	or	%l4, %l5, %l4				! put our number
	sll	%l4, 24, %l4
	sta	%l4, [%l6] ASI_PHYS_IO			! write back icr


With this in place, the kernel would correctly update its clock and generally live. And it was asking for its root filesystem...

To be continued...

[ Index ] [ Prev: El jardín de senderos que se bifurcan ]