Now Reading
(Mis)understanding RISC-V ecalls and syscalls

(Mis)understanding RISC-V ecalls and syscalls

2023-10-09 06:42:09

After spending a while with emuriscv
and trying in addition Linux right into a shell I noticed that I’m doing one thing actually mistaken concerning system calls.

RISC-V provides an ecall (Setting Name) instruction to implement system calls. These are principally requests made by a decrease privileged code (consumer mode) to execute greater privileged code (kernel). Or, in another case, the kernel itself may be the decrease privileged code and it will invoke the machine mode code with an ecall.

On the RISC-V platform this name can even act as a handy approach of offering enter/output, both from naked steel code or the kernel.

Enter the SBI for console output

In accordance with the SBI (supervisor binary interface), which I take for a BIOS equal within the RISC-V world, there’s is a legacy “console” interface
with two features:

void sbi_console_putchar(int ch)
int sbi_console_getchar(void)

This getchar/putchar pair interacts with a debug console. Linux occurs to supply a RISC-V SBI console driver
, that’s enabled with the HVC_RISCV_SBI configuration choice and calls into features applied in sbi.c
.

The implementation calls a operate named sbi_ecall, that generates the meeting code invoking the ecall and collects the return code (if any).

void sbi_console_putchar(int ch)
{
	sbi_ecall(SBI_EXT_0_1_CONSOLE_PUTCHAR, 0, ch, 0, 0, 0, 0, 0);
}

which will get compiled into the next meeting:

Doing it the mistaken approach

I misunderstood this and applied an ecall handler in my emulator that did seize the arguments and printed out the parameter into the usual output:

//ecall_callback
	if (state->x[SYSCALL_REG] == SBI_CONSOLE_PUTCHAR) {
		char c = (char)state->x[SBI_ARG0_REG];
		fprintf(stdout, "%c", c);
		state->x[SBI_RETURN_REG] = SBI_SUCCESS;
	}
    else if (state->x[SYSCALL_REG] == SBI_CONSOLE_GETCHAR) {
		//write invalid return worth within the register a0
		state->x[SBI_RETURN_REG] = -1;
	}

Whereas this technically did work and I received the Linux boot console output, I noticed fairly late that the identical ecall instruction can be used for one thing else than printing out characters to the display screen. The explanation it took so lengthy was that I discarded all the opposite ecall invocations in my implementation.

As ecall can be utilized by the user-mode packages to name into the kernel, it additionally meant that no system calls in anyway would get by means of to the kernel.

Doing it the suitable approach

RISC-V defines the next mechanism for truly dealing with the ecalls – they need to be an atomic leap to a managed location, dealt with by an exception handler.


image

There are the next exception causes that correspond to bits that may be set within the CSR_MEDELEG register. If the delegation bit on the specified index is about, then exception will get delegated to S mode, in any other case it’s dealt with in M mode.

title code
CAUSE_USER_ECALL 0x8
CAUSE_SUPERVISOR_ECALL 0x9
CAUSE_HYPERVISOR_ECALL 0xa
CAUSE_MACHINE_ECALL 0xb

Now, if we have now a easy binary that comprises these directions:

addi    a0, x0, 0   # Return code 0
addi    a7, x0, 93  # Syscall 93 terminates
ecall               # Name OS to terminate the program

The ecall raises an exception with the CAUSE_USER_ECALL, which will get trapped by the kernel lure handler and dealt with correctly.

Interactive MEDELEG CSR decoder

Enter the medeleg worth to see which exception bits it comprises.






See Also



Bit 0 1 2 3 4 5 6
Area MISALIGNED
FETCH
FETCH
ACCESS
ILLEGAL
INSTRUCTION
BREAKPOINT MISALIGNED
LOAD
LOAD
ACCESS
MISALIGNED
STORE
Worth


Bit 7 8 9 a b c d f
Area STORE
ACCESS
USER
ECALL
SUPERVISOR
ECALL
HYPERVISOR
ECALL
MACHINE
ECALL
FETCH
PAGE
FAULT
LOAD
PAGE
FAULT
STORE
PAGE
FAULT
Worth


MISA CSR register and platform capabilities identification

To permit BBL/OpenSBI to arrange lure handlers we must always inform it which extensions we help by organising the CSR_MISA register as follows, because it queries supports_extension('S') earlier than organising supervisor mode traps:

//RV32 IMAS  -> bits 0, 8, 12, 18, XLEN32 (bit 30)
state->csr[CSR_MISA] = 1 << 0 | 1 << 8 | 1 << 12 | 1 << 18 | 1 << 30;

Now we have now syscalls, however no console once more 🙁

Getting console again, which output driver to help?

Because the ecall is now correctly dealt with both by the OS or the OpenSBI, we’d like some strategy to produce output once more.

The sbi_console_putchar name from kernel is now trapped by OpenSBI/BBL by its sbi_console
module, that dispatches the character into a particular console driver.

As I’m utilizing each BBL and OpenSBI I used to be searching for one thing that’s simple to implement and out there in each loaders.

Driver BBL OpenSBI
SiFive UART ✔️ ✔️
8250/16550 UART ✔️ ✔️
LiteX
UART
✔️
HTIF ✔️ ✔️
Shakti UART ✔️

As a result of HTIF will not be actually supported on 32-bit (RV32) structure, it’s out. SiFive UART appeared easier than the 8250/16550 UART, with a few FIFO registers and flags.

Implementing SiFive UART in emuriscv

There are two components to a digital gadget – its implementation and making it discoverable.

The usual RISC-V mechanism of {hardware} discovery is through the gadget tree, so I needed to create an entry for this new UART gadget:

uart@10000000 {
    suitable = "sifive,uart0";
    reg = <0x00 0x10000000 0x00 0x100>;
};

ℹ️ We are able to flip binary FDT right into a text-based illustration by dtc -I dtb -O dts binary_fdt.dtb

This tells the OpenSBI/BBL to initialize the SiFive UART driver, pointing to the tackle 0x10000000. The counterpart on the emulator aspect is a memory-mapped gadget, which has a following write handler:

int32_t uart_reg[7];
#outline UART_REG_TXFIFO		0
#outline UART_REG_RXFIFO		1
...
static void uart_write(void* opaque, uint32_t offset, uint32_t val,
	int size_log2)
{
	int offset_words = offset >> 2;
	uart_reg[offset_words] = val;
	if (offset_words == UART_REG_TXFIFO) {
		if(val != 0) //skip null characters
			fputc(val, stderr);
	}
}

There’s the same uart_read() operate that simply returns no matter is contained in the UART registers.

I preferred this memory-mapped gadget mechanism from Bellard’s TinyEmu
and couldn’t consider something extra cheap.

How about console enter?

I’ve not applied it but as I’m nonetheless caught in a part the place I didn’t get usermode code to print out a single character. Nevertheless, an affordable implementation would ship a personality from the usual enter of the emulator with one thing like getch().

I’m nonetheless undecided whether or not that is all that’s wanted as I see many calls to sbi_console_getchar across the boot time, which might be simply polling for enter. I’m additionally getting a number of @^@^@^ sequences, which might truly imply one thing – an escape sequence {that a} terminal is meant to reply to, I’m not precisely positive at this cut-off date.

Source Link

What's Your Reaction?
Excited
0
Happy
0
In Love
0
Not Sure
0
Silly
0
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top