SerialICE Kernel Operation

Some of the code in this explanation is board-specific. This description uses code extracted from the kernel for the BDMR4101 evaluation board. Execution starts at the reset vector 0xbfc00000. At this address there is a branch-and-link to cpu_init.

reset_vector: # bfc00000
	bal	cpu_init
cpu_init performs any hardware specific initialization. eg. initialize the serial port that will be used to communicate with the SerialICE Controller, initialize the DRAM controller. It does not need to initialize the caches.

Once this step has been completed. We initialize the CAUSE and SR registers,

	# make sure the sw bits of the CAUSE register are zero
	.set noreorder
	mtc0	zero,C0_CAUSE		
	.set reorder

	# enable ints in SR MASK+IEC
	li	k0,(SR_BEV|SR_IEC|UART_INTBIT)
	.set noreorder
	mtc0	k0,C0_SR
	.set reorder
and then save some values in the savearea. Note that SAVEAREA has a kseg1 value.

	la	k0,SAVEAREA
	la	t0,get_word
	sw	t0,ICE_GWP*4(k0)
	la	t0,put_word
	sw	t0,ICE_PWP*4(k0)
	li	t0,IBUFSIZE
	sw	t0,ICE_IBS*4(k0)
	li	t0,REG_MAP
	sw	t0,ICE_MAP*4(k0)
	li	t0,SA_VERS
	sw	t0,ICE_SAV*4(k0)
	li	t0,ICE_SAHSIZE
	sw	t0,ICE_SAH*4(k0)
#ifdef MIPSEB
	li	t0,0
#else
	li	t0,1
#endif
	sw	t0,ICE_LE*4(k0)
This block of code writes seven values into the savearea. This information is needed by the SerialICE Driver or DLL. The savearea is in two parts, a header, and registers. The size of the header is placed in ICE_SAH, following this are a number of saved registers. Which registers, and how many, are indicated by the ICE_MAP value.

At this point the kernel permits you to transfer control to your application (if it is already in the ROM). Otherwise, it just sits in a tight loop waiting for a request from the SerialICE Driver or DLL.

#ifdef RUN_APPLICATION
	# execute a prom-resident application
	b	cstartup
#else
	# wait here for the host to speak to me
   1:	b	1b
#endif
When the kernel receives an interrupt, the hardware transfers control to the general exception vector. The kernel forces kseg1 (non cacheable) execution and then saves 9 registers in the savearea. These registers are required for the kernel's operation.

gen_vector: # bfc00180
	# save regs
	la	k0,SAVEAREA
	sw	AT,ICE_AT*4(k0)
	sw	v0,ICE_V0*4(k0)
	sw	a0,ICE_A0*4(k0)
	sw	a1,ICE_A1*4(k0)
	sw	a2,ICE_A2*4(k0)
	sw	a3,ICE_A3*4(k0)

	# make sure that we are in kseg1
	la	a3,1f
	li	a2,K1BASE
	or	a3,a2
	j	a3
   1:
	sw	t0,ICE_T0*4(k0)
	sw	t1,ICE_T1*4(k0)
	sw	t2,ICE_T2*4(k0)
	sw	t3,ICE_T3*4(k0)
	sw	t4,ICE_T4*4(k0)
	sw	s0,ICE_S0*4(k0)
	sw	ra,ICE_RA*4(k0)
	.set noreorder
	mfc0	t0,C0_EPC
	nop
	.set reorder
	sw	t0,ICE_EPC*4(k0)
Register s0 is used as a pointer to the current location in the memory-based instruction buffer. So we need to initialize it here.

	# init s0 (KSEG1)
	li	s0,INSTR_BUFFER
Now we need to find out what the cause of the exception was. The pseudo code is as follows:

	if (not hw int) goto send_ack;
	else if (not my int) goto send_ack;
	else if (not attn byte) goto restore_rfe;
	else goto send_ack;
If the exception was not a hardware interrupt jump to send_ack. If it was a hardware, but it wasn't a serial-port interrupt jump to send_ack. If it was a serial-port interrupt, but it wasn't a valid ATTN byte jump to restore_rfe. For all other cases jump to send_ack. The real code follows:

	# read the CAUSE register
	.set noreorder
	mfc0	a0,C0_CAUSE
	nop
	.set reorder

	# hw int?
	and	t0,a0,CAUSE_EXCMASK
	bne	t0,zero,send_ack	# brif not a hw int

	# It is a hw int. But is it my int?
	.set noreorder
	mfc0	t0,C0_SR
	nop
	.set reorder
	and	t0,a0		# qualify the CAUSE bits
	and	t0,UART_INTBIT
	beq	t0,zero,send_ack	# brif not mine

	# make sure that this is a *real* attn byte
	# read the byte
	li	a2,UART_BASE
	lw	k0,UART_RXHR(a2)

	li	k1,ATTN
	bne	k0,k1,restore_rfe	# brif not an attn byte

	# fall thru to send_ack
	.end gen_vector
send_ack sends the special ACK character. It also sets t0 and t1 to zero. These two registers are used by the SerialICE Driver (running on the SerialICE Controller) to hold the current address and the current data value. The driver assumes that these registers start with a value of zero, and so they must be initialized here.

send_ack:
	li	a2,UART_BASE

	# make sure that the tx is ready
   1:	lw 	k0,UART_TXS(a2)
	and	k0,TXS_TXRDY
	beq	k0,zero,1b

	li	k0,ACK
	sw	k0,UART_TXHR(a2)

	# make sure that r8 and r9 are zero.
	li	t0,0
	li	t1,0
	# fall thru to ice_loop
	.end send_ack
The next block of code is the main ice_loop. This is where the kernel remains while it is in control (state1). Pseudo code for this block follows:

	for (;;) {
		w = get_word();
		if (w == SENDA0) {
			*s0++ = 0; *s0++ = J_RA; *s0++ = 0;
			a0 = (* s0)();
			put_word(a0);
			}
		else if (w == RUN_MODE) {
			restore_regs;
			rfe;
			}
		else if (w == SENDSAP) {
			a0 = &savearea;
			put_word(a0);
			}
		else *s0++ = w;
		}
Each time around the loop, the kernel performs the following actions:

The real code for the ice_loop follows:

ice_loop:
	bal	get_cmd

   	# check for SENDA0
	li	a2,SENDA0
	bne	a2,v0,1f

	# It is SENDA0. Execute the code in INSTR_BUFFER and send 
	# the value of register a0.
	# Make sure that the routine ends with a "j ra".
	sw	zero,(s0)
	li	k0,J_RA_INSTR
	sw	k0,4(s0)
	sw	zero,8(s0)
	# Make sure that the writes complete before the jal.
	.set noreorder
	nop
	nop
	nop
	.set reorder
	# Reset s0 to point to start of INSTR_BUFFER.
	li	s0,INSTR_BUFFER
	jal	s0		# execute INSTR_BUFFER
	bal	put_word	# send A0
	b	ice_loop

   1:	# check for RUN_MODE
	li	a2,RUN_MODE
	bne	a2,v0,1f

restore_rfe:
	# It is RUN_MODE. Transfer control to the client.
	# restore regs
	la	k0,SAVEAREA
	lw	AT,ICE_AT*4(k0)
	lw	v0,ICE_V0*4(k0)
	lw	a0,ICE_A0*4(k0)
	lw	a1,ICE_A1*4(k0)
	lw	a2,ICE_A2*4(k0)
	lw	a3,ICE_A3*4(k0)
	lw	t0,ICE_T0*4(k0)
	lw	t1,ICE_T1*4(k0)
	lw	t2,ICE_T2*4(k0)
	lw	t3,ICE_T3*4(k0)
	lw	t4,ICE_T4*4(k0)
	lw	s0,ICE_S0*4(k0)
	lw	ra,ICE_RA*4(k0)
	.set noreorder
	lw	k0,ICE_EPC*4(k0)
	nop
	j	k0		# jump to client
	rfe
	.set reorder

   1:	# check for SENDSAP
	li	a2,SENDSAP
	bne	a2,v0,1f

	# It is SENDSAP. Send address of SAVEAREA.
	la	a0,SAVEAREA
	or	a0,1		# indicate new format
	bal	put_word
	b	ice_loop

   1:	# else. Not a special word.
	sw	v0,(s0)		# save word in INSTR_BUFFER
	addu	s0,4		# ready for next word
	b	ice_loop
	.end ice_loop
The get_cmd function reads 4 bytes from the serial port, and assembles them into a 32-bit word. The word is placed in register v0. This routine differs from the get_word routine that is used for download, in that it will always respond to an ATTN byte (0x55).

get_cmd:
	li	a2,UART_BASE
	li	a1,4			# get 4 bytes

	# wait for rxrdy
   3:   lw      k0,UART_RXS(a2)
	and	k0,RXS_RXRDY
        beq     k0,zero,3b

	# get the byte
        lw      k0,UART_RXHR(a2)

	# first byte?
	bne	a1,4,2f			# brif not first byte

	# is the byte a wakeup?
	bne	k0,ATTN,2f		# brif not a wakeup

	# wait for txrdy
   1:   lw      k0,UART_TXS(a2)
	and	k0,TXS_TXRDY
        beq     k0,zero,1b

	# send an ack
	li	k0,ACK
        sw      k0,UART_TXHR(a2)
	b	3b

   2:	sll	v0,8			# move word into position
	or	v0,k0			# merge byte with word
	subu	a1,1			# bytecount--
	bne	a1,zero,3b		# do next byte

	j	ra
	.end get_cmd
get_word:
        li      a2,UART_BASE
        li      a1,4

   1:   lw      k0,UART_RXS(a2)
	and	k0,RXS_RXRDY
        beq     k0,zero,1b

        lw      k0,UART_RXHR(a2)
        sll     v0,8
        or      v0,k0
        subu    a1,1
        bne     a1,zero,1b

        j       ra
        .end get_word
The put_word function transmits the 32-bit contents of register a0 as 4 successive bytes.

put_word:
        li      a2,UART_BASE
        li      a1,4

   1:   lw      k0,UART_TXS(a2)
	and	k0,TXS_TXRDY
        beq     k0,zero,1b

        sw      a0,UART_TXHR(a2)
        srl     a0,8
        subu    a1,1
        bne     a1,zero,1b

        j       ra
        .end put_word

Examples


Navigation: Document Home | Document Contents | Document Index