[RISC-V] Linux kernel - local_irq_enable() and sstatus.sie (Enable local interrupt)

[RISC-V] Linux kernel - local_irq_enable() and sstatus.sie (Enable local interrupt)

The Linux kernel provides API functions to disable or enable local interrupts:

  • local_irq_disable()
  • local_irq_enable()

Code Analysis: preempt_schedule_irq() Function

Now, let's analyze part of the preempt_schedule_irq() function:.

asmlinkage __visible void __sched preempt_schedule_irq(void)
{
	enum ctx_state prev_state;

	/* Catch callers which need to be fixed */
	BUG_ON(preempt_count() || !irqs_disabled());

	prev_state = exception_enter();

	do {
		preempt_disable();
		local_irq_enable();
		__schedule(SM_PREEMPT);
		local_irq_disable();
		sched_preempt_enable_no_resched();
	} while (need_resched());

	exception_exit(prev_state);
}        

In this code, the local_irq_enable() function is called to enable local interrupts while executing the schedule() function. After schedule() finishes, local_irq_disable() is called to disable local interrupts again.

Disassembly Analysis

Now, let’s compile this code using a RISC-V-based compiler and analyze the disassembled output:

SP:FFFFFFFF80D8E526|preempt_schedule_irq:  lw         a5,0x8(tp)    ; a5,8(tp)  
SP:FFFFFFFF80D8E52A| c.bnez     a5,0xFFFFFFFF80D8E55E  
[...]  
SP:FFFFFFFF80D8E53C| csrsi      sstatus,0x2  /// <<-- 
SP:FFFFFFFF80D8E540| c.li       a0,0x1  
SP:FFFFFFFF80D8E542| auipc      ra,0xFFFFF    ; ra,1048575  
SP:FFFFFFFF80D8E546| jalr       ra,0x516(ra)   ; ra,1302(ra) ; __schedule  
SP:FFFFFFFF80D8E54A| csrci      sstatus,0x2   /// <<--        

From this output, we can see that the csrsi sstatus,0x2 and csrci sstatus,0x2 instructions are used. These instructions set (1) or clear (0) the second bit of the sstatus register, respectively.

Internal Implementation of local_irq_enable()

The local_irq_enable() function is architecture-dependent. Let’s analyze its implementation step by step.

local_irq_enable() function is declared using the following macro:

// include/linux/irqflags.h  
#define local_irq_enable() do { raw_local_irq_enable(); } while (0)        

This function is replaced by raw_local_irq_enable() during compliatin. Let's analyze raw_local_irq_enable() function:

// include/linux/irqflags.h  
#define raw_local_irq_enable() arch_local_irq_enable()        

This function is then replaced by arch_local_irq_enable().

The Linux kernel often uses macros to ensure that these kinds of macro function, even through it is executing for different architectures.

Let's look into arch_local_irq_enable() function:

// arch/riscv/include/asm/irqflags.h  
static inline void arch_local_irq_enable(void)  
{  
	csr_set(CSR_STATUS, SR_IE);  
}        

This function is implemented specifically for the RISC-V architecture, if we refer to the source path. This inline type function sets the SR_IE bit in the sstatus register to enable interrupts.

Let's review the code that represents SR_IE and SR_SIE:


// arch/riscv/include/asm/csr.h  
#define SR_IE  SR_SIE  
[...]  
#define SR_SIE _AC(0x00000002, UL)  /* Supervisor Interrupt Enable */        

Here, SR_IE is defined as 0x2. This matches the instruction csrsi sstatus,0x2, which sets the second bit of the sstatus register.

Summary of Code Analysis:

The local_irq_enable() function enables local interrupts.

In the RISC-V architecture, the "csrsi sstatus,0x2" instruction is actual code for local_irq_enable() function. This instruction sets the second bit of the sstatus register to 1.

Hardware Debugging: Checking the sstatus Register

No matter how we try to analyze instruction in RISC-V, it is hard to identify the bit flag inside sstatus for "csrsi sstatus,0x2" instruction. We have powerful hardware debugging - TRACE32 that shows a set of CSR at RISC-V architecture level.

Let's take a look at the following screen shot:

Article content

What we can see above is that sstatus register value changes to 0x2 after running "csrsi sstatus,0x2". As a result, the local interrupt is enabled. The reason is - if we set sstatus.sie to 1, the local interrupt is enabled.

Let's take a look at the another screen shot:

Article content

What we can see above is that sstatus register value changes to 0x0 after running csrci sstatus,0x2. As a result, the local interrupt is disabled. The reason is - if we set sstatus.sie to 0, the local interrupt is disabled.

Insight:

Through debugging, we have gained a clear understanding of how the local_irq_enable() function works and how the sstatus register plays a role in enabling and disabling local interrupts. What is analyze helps us understand the interaction between software and hardware when managing interrupts.

Ankit Chauhan

Linux Kernel| OS Internals | Virtualization | Embedded Systems | Secure Boot | ARM/x86 | eBPF | Device Drivers

1mo

Austin Kim Great post about what and how mechanisms regarding the local interrupts in the linux kernel. It would be awesome if you could also add, why we need to enable/disable local interrupts and the use cases 🙂

To view or add a comment, sign in

More articles by Austin Kim

Insights from the community

Others also viewed

Explore topics