Yeah, if you can't use the toy system calls, read a string and do total = total*10 + digit on it, where digit = c-'0'.  You'll need to do extended-precision multiply, so it's probably easier to do extended-precision shifts like (total << 3) + (total << 1).
Check compiler output on Godbolt. For example, GCC using shifts, clang using mul/mulhu(high unsigned) for the lo * lo 32x32=>64-bit partial product, and a mul for the high half cross product (hi * lo).  It's fewer instructions, but depends on a RISC-V CPU with a fast multiplier to be faster than shift/or.
(RISC-V extended-precision addition is inconvenient since it doesn't have a carry flag, you need to emulate carry-out as unsigned sum = a+b; carry = sum<a;)
#include <stdint.h>
uint64_t strtou64(unsigned char*p){
    uint64_t total = 0;
    unsigned digit = *p - '0';    // peeling the first iteration is usually good in asm
    while (digit < 10) {     // loop until any non-digit character
        total = total*10 + digit;
        p++;                // *p was checked before the loop or last iteration
        digit = *p - '0';   // get a digit ready for the loop branch
    }
    return total;
}
Clang's output is shorter, so I'll show it.  It of course follows the standard calling convention, taking the pointer in a0, and returning a 64-bit integer in a pair of registers, a1:a0:
# rv32gc clang 14.0  -O3
strtou64:
        mv      a2, a0
        lbu     a0, 0(a0)         # load the first char
        addi    a3, a0, -48       # *p - '0'
        li      a0, 9
        bltu    a0, a3, .LBB0_4   # return 0 if the first char is a non-digit
        li      a0, 0               # total in a1:a0 = 0   ;  should have done these before the branch
        li      a1, 0                           # so a separate ret wouldn't be needed
        addi    a2, a2, 1           # p++
        li      a6, 10              # multiplier constant
.LBB0_2:                            # do{
        mulhu   a5, a0, a6            # high half of (lo(total) * 10)
        mul     a1, a1, a6            # hi(total) * 10
        add     a1, a1, a5            # add the high-half partial products
        mul     a5, a0, a6            # low half of  (lo(total) * 10)
        lbu     a4, 0(a2)                # load *p
        add     a0, a5, a3            # lo(total) =  lo(total*10) + digit
        sltu    a3, a0, a5            # carry-out from that
        add     a1, a1, a3            # propagate carry into hi(total)
        addi    a3, a4, -48             # digit = *p - '0'
        addi    a2, a2, 1                # p++ done after the load; clang peeled one pointer increment before the loop
        bltu    a3, a6, .LBB0_2     # }while(digit < 10)
        ret
.LBB0_4:
        li      a0, 0               # return 0 special case
        li      a1, 0               # because clang was dumb and didn't load these regs before branching
        ret
If you want to go with GCC's shift/or strategy, it should be straightforward to see how that slots in to the same logic clang is using.  You can look at compiler output for a function like return u64 << 3 to see which instructions are part of that.
And BTW, I wrote the C with compiling to decent asm in mind, making it easy for the compiler to transform it into a do{}while loop with the condition at the bottom.  I based it on the x86 asm in my answer on NASM Assembly convert input to integer?