The only registers that need to be initialised before your C code runs are the global pointer (gp) and the stack pointer (sp). You may also need to set the exception temporary (et) if your execption handler does anything (mine just saves the registers into a fixed memory area and loopstops!).
Clearly the libc stdio and malloc functions do require more initialisation - but for a small embedded system you may not need those at all.
In the linker script you can Build the instructions by hand, something like:
RA = 32 - 5;
RB = RA - 5;
RC = RB - 5;
IMM16 = 6;
SET_GP_HI = 0 << RA | 26 << RB | ((_gp + 32768) >> 16) << IMM16 | 0x34;
SET_GP_LO = 26 << RA | 26 << RB | (_gp & 0xffff) << IMM16 | 0x4;
/* addi instructions to set %sp and %et from %gp */
SET_SP = 26 << RA | 27 << RB | 0x4;
SET_ET = 26 << RA | 24 << RB | 0x4;
SECTIONS
{
/* code section, at reset address */
code : {
LONG(SET_GP_HI)
LONG(SET_GP_LO)
LONG(SET_SP | ((stack_top - _gp) & 0xffff) << IMM16)
LONG(SET_ET | ((exception_data - _gp) & 0xffff) << IMM16)
LONG(c_code << 4 | 1) /* jmpi c_code() */
You need to set the stack pointer because gcc always saves the caller-saved registers - even for functions with __attribute__(noreturn).
Similar stuff can be done to save/restore registers for the exception handler.