UART Interrupt and Platform-Degree Interrupt Controller (PLIC)
📝 3 Dec 2023
“It’s time for the little pink hen’s bedtime story – and a reminder from Papa to attempt to not interrupt. However the hen can’t assist herself!”
Our Story at this time is all about RISC-V Interrupts on the tiny cute Pine64 Ox64 BL808 64-bit Single-Board Pc (pic under)…
-
What’s contained in the Platform-Degree Interrupt Controller (PLIC)
-
Establishing the PLIC at startup
-
Enabling the PLIC Interrupt for Serial Console
-
Dealing with PLIC Interrupts for UART Enter
We’ll stroll via the steps with a easy barebones working system: Apache NuttX RTOS. (Actual-Time Working System)
Although we’ll hit a bumpy journey with our work-in-progress NuttX on Ox64…
We start our story…
What’s this PLIC?
Platform-Level Interrupt Controller (PLIC) is the {hardware} inside our BL808 SoC that controls the forwarding of Peripheral Interrupts to our 64-bit RISC-V CPU.
(Just like the Interrupts for UART, I2C, SPI, …)
Why ought to we trouble with PLIC?
Suppose we’re utilizing the Serial Console on Ox64 SBC (pic under)…
-
Each single key that we press…
-
Will fireplace an Interrupt via the PLIC to the RISC-V CPU
-
With out the PLIC, it’s unattainable to enter instructions within the Serial Console!
That’s why it’s good to grasp how PLIC works with an Working System. (Like Linux or NuttX)
PLIC handles all types of Interrupts?
Yep loads! We determine every Interrupt by its RISC-V IRQ Quantity. (IRQ means Interrupt Request Quantity)
NuttX makes use of its personal NuttX IRQ Quantity…
- NuttX IRQ = 25 + RISC-V IRQ
That’s as a result of NuttX reserves a bunch of IRQ Numbers for Inside Use. (Therefore the Offset of 25)
Let’s work out the IRQ Quantity for Serial Console…
(PLIC is documented in C906 User Manual, Page 74)
What’s the Interrupt Quantity for the Serial Console?
To allow Textual content Enter within the Ox64 Serial Console, we want the Interrupt Quantity for the UART Controller…
-
We’re working on the D0 Multimedia Core of the BL808 SoC
(Pic above)
-
Related to the D0 Multimedia Core is the UART3 Controller for Serial Console
(Pic under)
-
In keeping with the desk under: RISC-V IRQ Quantity for UART3 is…
IRQ_NUM_BASE + 4
-
Additionally within the desk…
IRQ_NUM_BASE is 16
Subsequently the RISC-V IRQ Quantity for our Serial Console (UART3) is 20.
Keep in mind that NuttX makes use of its personal NuttX IRQ Quantity…
- NuttX IRQ = 25 + RISC-V IRQ
Later we’ll deal with NuttX IRQ Quantity 45 in our code. And our Ox64 Serial Console will help Textual content Enter!
How did we get the UART Driver for Ox64 BL808?
We copied the NuttX UART Driver from BL602 to BL808, for the reason that UART Controllers are comparable…
BL808 Reference Manual (Page 44)
How we could get began with PLIC?
We stroll via the steps to put together the Platform-Degree Interrupt Controller (PLIC) at startup…
-
Disable all Interrupts
(As a result of we’re about to configure them)
-
Clear the Excellent Interrupts
(So we received’t get caught at startup)
-
Set the Interrupt Precedence
(To the Lowest Precedence)
-
Set the Interrupt Threshold
(Permitting Interrupts to be fired later)
We start by disabling all Interrupts in PLIC.
Writing 0 to the Interrupt Allow Register (pic above) will disable all PLIC Interrupts: jh7110_irq.c
// Init the Platform-Degree Interrupt Controller
void up_irqinitialize(void) {
// Disable Supervisor-Mode Interrupts (SIE Register)
up_irq_save();
// Disable all Exterior Interrupts
// PLIC_ENABLE1 is 0xE000_2080
// PLIC_ENABLE2 is 0xE000_2084
// putreg32(V, A) writes 32-bit worth V to handle A
putreg32(0x0, PLIC_ENABLE1); // RISC-V IRQ 1 to 31
putreg32(0x0, PLIC_ENABLE2); // RISC-V IRQ 32 to 63
Therefore at startup, all PLIC Interrupts are disabled till we allow them later (in PLIC).
(PLIC_ENABLE and other PLIC Offsets)
(NuttX calls up_irqinitialize at startup)
Subsequent we Declare and Full the Excellent Interrupts, so that they received’t trouble us at startup (pic above): jh7110_irq.c
// Declare and Full the Excellent Interrupts
// PLIC_CLAIM is 0xE020_1004
// getreg32(A) reads a 32-bit worth from deal with A
uintptr_t val = getreg32(PLIC_CLAIM);
putreg32(val, PLIC_CLAIM);
(Extra about Declare and Full shortly)
We initialise the Interrupt Precedence of all Interrupts to 1 (pic above): jh7110_irq.c
// Set Precedence for all Exterior Interrupts to 1 (Lowest)
// NR_IRQS is 83 (TODO: BL808 solely helps 82 Peripheral Interrupts)
// PLIC_PRIORITY is 0xE000_0000
for (int id = 1; id <= NR_IRQS; id++) {
putreg32(
1, // Worth
(uintptr_t)(PLIC_PRIORITY + 4 * id) // Handle
);
}
Why set Interrupt Precedence to 1?
-
1 is the Lowest Interrupt Precedence
-
Default Interrupt Precedence is 0, however it’s not legitimate
-
Interrupt received’t really fireplace till we allow it later (in PLIC)
Lastly we set the Interrupt Threshold to 0 (pic above): jh7110_irq.c
// Set Interrupt Threshold to 0
// (Permits all Exterior Interrupts)
// PLIC_THRESHOLD is 0xE020_1000
putreg32(0, PLIC_THRESHOLD);
// Connect the Widespread RISC-V Exception Handlers
// TODO: Do that earlier
riscv_exception_attach();
// Allow Supervisor-Mode Interrupts (SIE Register)
up_irq_enable();
}
(riscv_exception_attach is here)
Why set Interrupt Threshold to 0?
-
Earlier we set the Interrupt Precedence to 1 for All Interrupts
-
Since Interrupt Precedence > Interrupt Threshold (0)…
All Interrupts will likely be allowed to fireplace
-
Keep in mind: Interrupts received’t really fireplace till we allow them later (in PLIC)
And we’re executed initing the PLIC at startup!
Our Platform-Degree Interrupt Controller (PLIC) is all prepared for motion…
How will we allow Interrupts in PLIC?
Suppose we’re enabling RISC-V IRQ 20 for UART3 Interrupt.
All we have to do is to flip Bit 20 to 1 within the Interrupt Allow Register (pic above). Like so: jh7110_irq.c
// Allow the NuttX IRQ specified by `irq`
// UART3 Interrupt is RISC-V IRQ 20
// Which is NuttX IRQ 45 (Offset by 25)
void up_enable_irq(int irq) {
// Omitted: Allow Inter-CPU Interrupts (SIE Register)
// Omitted: Allow Timer Interrupts (TIE Register)
// If that is an Exterior Interrupt...
if (irq > RISCV_IRQ_EXT) {
// Subtract 25 from NuttX IRQ to get the RISC-V IRQ
int extirq = irq - RISCV_IRQ_EXT;
// Set the Interrupt Allow Bit for `extirq` in PLIC
// PLIC_ENABLE1 is 0xE000_2080
// PLIC_ENABLE2 is 0xE000_2084
if (0 <= extirq && extirq <= 63) {
modifyreg32(
PLIC_ENABLE1 + (4 * (extirq / 32)), // Handle
0, // Clear Bits
1 << (extirq % 32) // Set Bits
);
}
else { PANIC(); } // IRQ not supported (for now)
}
}
And PLIC will fortunately settle for RISC-V IRQ 20 at any time when we press a key!
(On the Serial Console, pic above)
Who calls up_enable_irq?
At startup, NuttX calls bl602_attach to connect the UART Interrupt Handler…
// Connect UART Interrupt Handler
static int bl602_attach(struct uart_dev_s *dev) {
...
// Allow Interrupt for UART3.
// `irq` is NuttX IRQ 45
up_enable_irq(priv->irq);
Which is able to name up_enable_irq to allow the UART3 Interrupt.
We’re midway via our Grand Plan of PLIC Interrupts! (Steps 1, 2 and three, pic under)
We pause a second to speak about Harts…
The pic above: Why does it say “Hart 0, Supervisor Mode”?
“Hart” is a RISC-V CPU Core.
(“{Hardware} Thread”)
“Hart 0” refers back to the (one and solely) 64-bit RISC-V Core contained in the BL808 SoC…
That runs our NuttX RTOS.
Does the Hart Quantity matter?
Most actually! Contained in the StarFive JH7110 SoC (for Star64 SBC), there are 5 Harts…
NuttX boots on Hart 1. So the PLIC Settings will use Hart 1. (Not Hart 0)
And the PLIC Register Offsets are totally different for Hart 0 vs Hart 1. Thus the Hart Quantity actually issues!
Why “Supervisor Mode”?
-
RISC-V Machine Mode is probably the most highly effective mode in our RISC-V SBC.
OpenSBI Supervisor Binary Interface runs in Machine Mode.
(It’s like BIOS for RISC-V)
-
RISC-V Supervisor Mode is much less highly effective than Machine Mode.
NuttX Kernel runs in Supervisor Mode.
(Linux too!)
-
RISC-V Consumer Mode is the least highly effective mode.
NuttX Apps run in Consumer Mode.
(Identical for Linux Apps)
PLIC has a unique set of registers for Machine Mode vs Supervisor Mode.
That’s why we specify Supervisor Mode for the PLIC Registers.
What concerning the registers WITHOUT “Hart 0, Supervisor Mode”?
These are the Widespread PLIC Registers, shared throughout all Harts and RISC-V Modes.
Heading again to our (interrupted) story…
What occurs after we press a key on the Serial Console? (Pic above)
How will PLIC deal with the UART Interrupt?
That is how we deal with an Interrupt with the Platform-Degree Interrupt Controller (PLIC)…
-
Declare the Interrupt
(To acknowledge the Interrupt)
-
Dispatch the Interrupt
(Name the Interrupt Handler)
-
Full the Interrupt
(Inform PLIC we’re executed)
-
Non-compulsory: Examine and reset the Pending Interrupts
(In case we’re actually curious)
How will we all know which RISC-V Interrupt has been fired?
That’s why now we have the Interrupt Declare Register! (Pic above)
We learn the Interrupt Declare Register to get the RISC-V IRQ Quantity that has been fired (20 for UART3): jh7110_irq.c
// Dispatch the RISC-V Interrupt
void *riscv_dispatch_irq(uintptr_t vector, uintptr_t *regs) {
// Compute the (Interim) NuttX IRQ Quantity
// Based mostly on the Interrupt Vector Quantity
int irq = (vector >> RV_IRQ_MASK) | (vector & 0xf);
// If that is an Exterior Interrupt...
if (RISCV_IRQ_EXT == irq) {
// Learn the RISC-V IRQ Quantity
// From PLIC Declare Register
// Which additionally Claims the Interrupt
// PLIC_CLAIM is 0xE020_1004
uintptr_t val = getreg32(PLIC_CLAIM);
// Compute the Precise NuttX IRQ Quantity:
// RISC-V IRQ Quantity + 25 (RISCV_IRQ_EXT)
irq += val;
}
// For UART3: `val` is 20 and `irq` is 45
// Up Subsequent: Dispatch and Full the Interrupt
What precisely are we “claiming”?
After we Claim an Interrupt (by studying the Interrupt Declare Register)…
We’re telling the PLIC: “Sure we acknowledge the Interrupt, however we’re not executed but!”
Shortly we will Full the Interrupt. (To inform PLIC we’re executed)
(riscv_dispatch_irq is called by the RISC-V Common Exception Handler)
We’ve got Claimed the Interrupt. It’s time to do some work: jh7110_irq.c
// Omitted: Declare the Interrupt
...
// Keep in mind: `irq` is now the ACTUAL NuttX IRQ Quantity:
// RISC-V IRQ Quantity + 25 (RISCV_IRQ_EXT)
// For UART3: `irq` is 45
// If the RISC-V IRQ Quantity is legitimate (non-zero)...
if (RISCV_IRQ_EXT != irq) {
// Name the Interrupt Handler
regs = riscv_doirq(irq, regs);
}
// Up Subsequent: Full the Interrupt
For UART Interrupts: riscv_doirq will name uart_interrupt to deal with the keypress.
(That’s as a result of at startup, bl602_attach has registered uart_interrupt because the UART Interrupt Handler)
To inform PLIC we’re executed, we write the RISC-V IRQ Quantity (20) again to the Interrupt Declare Register.
(Yep the identical one we learn earlier! Pic above)
This can Complete the Interrupt, so PLIC can fireplace the following one: jh7110_irq.c
// Omitted: Declare and Dispatch the Interrupt
...
// Keep in mind: `irq` is now the ACTUAL NuttX IRQ Quantity:
// RISC-V IRQ Quantity + 25 (RISCV_IRQ_EXT)
// For UART3: `irq` is 45
// If that is an Exterior Interrupt (RISCV_IRQ_EXT = 25)...
if (RISCV_IRQ_EXT <= irq) {
// Compute the RISC-V IRQ Quantity (20 for UART3)
// and Full the Interrupt.
// PLIC_CLAIM is 0xE020_1004
putreg32( // We write the...
irq - RISCV_IRQ_EXT, // RISC-V IRQ Quantity (RISCV_IRQ_EXT = 25)
PLIC_CLAIM // To PLIC Declare (Full) Register
);
}
// Return the Registers to the Caller
return regs;
}
And that’s how we deal with a PLIC Interrupt!
What’s with the Pending Interrupts?
Usually the Interrupt Declare Register is completely satisfactory for dealing with Interrupts.
But when we’re actually curious: PLIC has an Interrupt Pending Register (pic above) that may inform us which Interrupts are awaiting Claiming or Completion: jh7110_irq.c
// Examine the Pending Interrupts...
// Learn PLIC_IP0: Interrupt Pending for interrupts 1 to 31
uintptr_t ip0 = getreg32(0xe0001000);
// If Bit 20 is about...
if (ip0 & (1 << 20)) {
// Then UART3 Interrupt was fired (RISC-V IRQ 20)
val = 20;
}
To inform PLIC we’re executed: We clear the Particular person Bits within the Interrupt Pending Register: jh7110_irq.c
// Clear the Pending Interrupts...
// Set PLIC_IP0: Interrupt Pending for interrupts 1 to 31
putreg32(0, 0xe0001000);
// TODO: Clear the Particular person Bits as an alternative of wiping out the Total Register
As soon as once more, we don’t want really want this. We’ll stash this as our Backup Plan in case issues go incorrect.
(Oh sure, issues will go incorrect shortly)
I sense a twist in our story…
Earlier we initialised the Interrupt Priorities to 1 at startup (pic above): jh7110_irq.c
// Init the Platform-Degree Interrupt Controller
void up_irqinitialize(void) {
...
// Set Precedence for all Exterior Interrupts to 1 (Lowest)
// NR_IRQS is 83 (TODO: BL808 solely helps 82 Peripheral Interrupts)
// PLIC_PRIORITY is 0xE000_0000
for (int id = 1; id <= NR_IRQS; id++) {
putreg32(
1, // Worth
(uintptr_t)(PLIC_PRIORITY + 4 * id) // Handle
);
}
// Dump the Interrupt Priorities
infodumpbuffer("PLIC Interrupt Precedence: After", 0xe0000004, 0x50 * 4);
After we boot NuttX on Ox64, one thing unusual occurs…
PLIC Interrupt Precedence: After (0xe0000004):
0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
The whole lot turns into zero! Why???
Yeah that is completely baffling! And no Interrupts get fired, as a result of Interrupt Precedence 0 is NOT legitimate.
Let’s set the Interrupt Precedence particularly for RISC-V IRQ 20 (UART3 Interrupt): bl602_serial.c
// Take a look at the setting of PLIC Interrupt Precedence
// For RISC-V IRQ 20 solely
void test_interrupt_priority(void) {
// Learn the values earlier than setting Interrupt Precedence
uint32_t before50 = *(risky uint32_t *) 0xe0000050UL; // RISC-V IRQ 20
uint32_t before54 = *(risky uint32_t *) 0xe0000054UL; // RISC-V IRQ 21
// Set the Interrupt Precedence
// for 0x50 (IRQ 20) however NOT 0x54 (IRQ 21)
*(risky uint32_t *) 0xe0000050UL = 1;
// Learn the values after setting Interrupt Precedence
uint32_t after50 = *(risky uint32_t *) 0xe0000050UL; // RISC-V IRQ 20
uint32_t after54 = *(risky uint32_t *) 0xe0000054UL; // RISC-V IRQ 21
// Dump earlier than and after values:
_info("before50=%u, before54=%u, after50=%u, after54=%un",
before50, before54, after50, after54);
}
Once more we get odd outcomes (pic under)…
before50=0, before54=0
after50=1, after54=1
IRQ 20 is about appropriately: “after50=1”
Nevertheless IRQ 21 can be set! “after54=1”
Hmmm… Our writing appears to have leaked over to the following 32-bit phrase?
Yeah we see the Leaky Write once more after we set the Interrupt Enable Register…
// Earlier than setting Interrupt Allow: The whole lot is 0
PLIC Hart 0 S-Mode Interrupt Allow: Earlier than (0xe0002080):
0000 00 00 00 00 00 00 00 00 ........
// Set Interrupt Allow for RISC-V IRQ 20 (Bit 20)
up_enable_irq: extirq=20, addr=0xe0002080, val=0x1048576
// After setting Interrupt Allow:
// Bit 20 can be set within the subsequent phrase!
PLIC Hart 0 S-Mode Interrupt Allow: After (0xe0002080):
0000 00 00 10 00 00 00 10 00 ........
Interrupt Allow has leaked over from 0xE000
2080
to 0xE000
2084
!
Thus now we have an unexplained downside of Leaky Writes, affecting the Interrupt Precedence and Interrupt Allow Registers.
Up Subsequent: Extra worries…
We talked earlier about Handling Interrupts…
And the way we fetch the RISC-V IRQ Quantity from the Interrupt Claim Register: jh7110_irq.c
// Dispatch the RISC-V Interrupt
void *riscv_dispatch_irq(uintptr_t vector, uintptr_t *regs) {
// Compute the (Interim) NuttX IRQ Quantity
int irq = (vector >> RV_IRQ_MASK) | (vector & 0xf);
// If that is an Exterior Interrupt...
if (RISCV_IRQ_EXT == irq) {
// Learn the RISC-V IRQ Quantity
// From PLIC Declare Register
// Which additionally Claims the Interrupt
// PLIC_CLAIM is 0xE020_1004
uintptr_t val = getreg32(PLIC_CLAIM);
What occurs after we run this?
On Ox64 we see NuttX booting usually to the NuttX Shell…
NuttShell (NSH) NuttX-12.0.3
nsh>
After we press a key on the Serial Console (to set off a UART Interrupt)…
riscv_dispatch_irq:
declare=0
Our Interrupt Handler says that the Interrupt Declare Register is 0…
Which suggests we are able to’t learn the RISC-V IRQ Quantity!
We activate our Backup Plan…
What’s our Backup Plan for Dealing with Interrupts?
We are able to get the RISC-V IRQ Quantity by studying the Interrupt Pending Register (pic above): jh7110_irq.c
// If Interrupt Claimed is 0...
if (val == 0) {
// Examine the Pending Interrupts...
// Learn PLIC_IP0: Interrupt Pending for interrupts 1 to 31
uintptr_t ip0 = getreg32(0xe0001000);
// If Bit 20 is about...
if (ip0 & (1 << 20)) {
// Then UART3 Interrupt was fired (RISC-V IRQ 20)
val = 20;
}
}
// Compute the Precise NuttX IRQ Quantity:
// RISC-V IRQ Quantity + 25 (RISCV_IRQ_EXT)
irq += val;
// Omitted: Name the Interrupt Handler
// and Full the Interrupt
Which tells us the proper RISC-V IRQ Quantity for UART3 yay!
riscv_dispatch_irq:
irq=45
(NuttX IRQ 45 means RISC-V IRQ 20)
Don’t overlook to clear the Pending Interrupts: jh7110_irq.c
// Clear the Pending Interrupts
// TODO: Clear the Particular person Bits as an alternative of wiping out the Total Register
putreg32(0, 0xe0001000); // PLIC_IP0: Interrupt Pending for interrupts 1 to 31
putreg32(0, 0xe0001004); // PLIC_IP1: Interrupt Pending for interrupts 32 to 63
// Dump the Pending Interrupts
infodumpbuffer("PLIC Interrupt Pending", 0xe0001000, 2 * 4);
// Yep works nice, Pending Interrupts have been cleared...
// PLIC Interrupt Pending (0xe0001000):
// 0000 00 00 00 00 00 00 00 00 ........
Does it work for UART Enter?
Since we’ve appropriately recognized the IRQ Quantity, riscv_dispatch_irq will (finally) name bl602_receive to learn the UART Enter (pic under)…
bl602_receive: rxdata=-1
bl602_receive: rxdata=0x0
However the UART Input is empty! We have to troubleshoot our UART Driver some extra.
In the meantime we wrap up our story for at this time…
Seems like we’re wading into murky greyish territory… Like Jaws meets Twilight Zone on the Seashore?
Yeah we stated this last time, and it’s occurring now…
“If RISC-V ain’t RISC-V on SiFive vs T-Head: We’ll discover out!”
The PLIC Code on this article was initially examined OK with…
-
StarFive JH7110 SoC in RISC-V Supervisor Mode
(Based mostly on SiFive U74 Core)
-
T-Head C906 Core in RISC-V Machine Mode
(Ox64 BL808 runs on the C906 Core)
-
However NOT T-Head C906 in RISC-V Supervisor Mode
(Which could clarify our troubles)
As we speak we’re hitting 2 Unusual Points within the BL808 (C906) PLIC…
-
Leaky Writes to PLIC Registers
(Writing to 1 register will have an effect on the following)
-
PLIC Claim Register all the time reads as 0
(As a substitute of RISC-V Exterior Interrupt Quantity)
Which shouldn’t occur as a result of PLIC is within the Official RISC-V Spec! So many questions…
-
Any clue what’s inflicting this?
Leaky Writes don’t appear to occur before enabling the MMU (Reminiscence Administration Unit)…
// Earlier than enabling Reminiscence Mgmt Unit... jh7110_mm_init: Take a look at Interrupt Precedence // No Leaky Writes! test_interrupt_priority: before50=0, before54=0 after50=1, after54=0 // Leaky Writes after enabling Reminiscence Mgmt Unit jh7110_kernel_mppings: map I/O areas
So it may be an issue with our MMU Settings.
-
What if we configure the MMU otherwise?
We moved the PLIC from Level 2 Page Tables as much as Level 1…
Identical downside.
-
One thing particular concerning the C906 MMU?
In keeping with the C906 User Manual (Web page 53), the C906 MMU helps Prolonged Web page Attributes.
Is the MMU Caching inflicting points? What’s “sysmap.h”?
-
What concerning the C906 PLIC?
In keeping with the Linux PLIC Driver…
“The T-HEAD C9xx SoC implements a modified/customized T-HEAD PLIC
specification which can masks present IRQ upon learn to CLAIM register
and can unmask the IRQ upon write to CLAIM register”Will this have an effect on our Interrupt Declare?
-
Possibly the GCC Compiler didn’t generate the best code?
We wrote RISC-V Assembly, disabling DCACHE / ICACHE and with SFENCE.
Nonetheless the identical.
-
Maybe our downside is Leaky Reads? Not Leaky Writes?
Hmmm… Maybe!
-
So RISC-V ain’t RISC-V on SiFive vs T-Head?
It feels… Very totally different? Evaluate the docs…
Can we rewrite our Unhappy Story with a Happier Conclusion? Please lemme know! 🙏
As we speak we talked about Interrupting Rooster, Oxen and Ox64 BL808 RISC-V SBC…
-
We appeared contained in the Platform-Degree Interrupt Controller (PLIC)
-
And arrange the PLIC at startup
-
We enabled the PLIC Interrupt for Serial Console
-
Additionally dealt with PLIC Interrupts for UART Enter
-
However we hit some Leaky Writes that impacts adjoining PLIC Registers
-
Sadly Interrupt Declare doesn’t work as anticipated
-
Thus we activated our Backup Plan with the Interrupt Pending Register
We’ve got loads to repair for NuttX on Ox64 BL808. Keep tuned for updates!
Many Because of my GitHub Sponsors (and the superior NuttX Group) for supporting my work! This text wouldn’t have been attainable with out your help.
Acquired a query, remark or suggestion? Create an Situation or submit a Pull Request right here…
lupyuen.github.io/src/plic2.md
How did we create the NuttX UART Driver for Ox64 BL808?
As we speak NuttX helps the 32-bit predecessor of BL808: Bouffalo Lab BL602.
After we examine these UARTs…
We uncover that BL808 UART works the identical means as BL602!
Thus we’ll merely copy the NuttX Driver for BL602 UART to Ox64.
Right here’s the UART Driver ported to BL808: bl602_serial.c
What did we modify?
We hardcoded the UART3 Base Handle: bl602_uart.h
// UART3 Base Handle
#outline BL602_UART0_BASE 0x30002000
#outline BL602_UART_BASE(n) (BL602_UART0_BASE)
Then we set the UART3 Interrupt Quantity: bl602_serial.c
// From BL808 Handbook: UART3 Interrupt = (IRQ_NUM_BASE + 4)
// The place IRQ_NUM_BASE = 16
// So RISC-V IRQ = 20
// And NuttX IRQ = 45 (Offset by 25)
#outline BL602_IRQ_UART0 45
And we modified the NuttX Begin Code to name our new UART Driver: jh7110_start.c
// At Startup, init the brand new UART Driver
void riscv_earlyserialinit(void) {
bl602_earlyserialinit();
}
// Identical right here
void riscv_serialinit(void) {
bl602_serialinit();
}
Lastly we enabled and dealt with the UART Interrupt…
After making these modifications, the UART Driver works OK for printing output to the Ox64 Serial Console!
(However not but for UART Enter, pic under)
On this article, we ran a Work-In-Progress Model of Apache NuttX RTOS for Ox64, with PLIC partially working.
(Console Enter will not be but mounted)
That is how we obtain and construct NuttX for Ox64 BL808 SBC…
## Obtain the WIP NuttX Supply Code
git clone
--branch ox64b
https://github.com/lupyuen2/wip-pinephone-nuttx
nuttx
git clone
--branch ox64b
https://github.com/lupyuen2/wip-pinephone-nuttx-apps
apps
## Construct NuttX
cd nuttx
instruments/configure.sh star64:nsh
make
## Export the NuttX Kernel
## to `nuttx.bin`
riscv64-unknown-elf-objcopy
-O binary
nuttx
nuttx.bin
## Dump the disassembly to nuttx.S
riscv64-unknown-elf-objdump
--syms --source --reloc --demangle --line-numbers --wide
--debugging
nuttx
>nuttx.S
2>&1
(Remember to install the Build Prerequisites and Toolchain)
(And enable Scheduler Info Output)
Then we construct the Preliminary RAM Disk that comprises NuttX Shell and NuttX Apps…
## Construct the Apps Filesystem
make -j 8 export
pushd ../apps
./instruments/mkimport.sh -z -x ../nuttx/nuttx-export-*.tar.gz
make -j 8 import
popd
## Generate the Preliminary RAM Disk `initrd`
## in ROMFS Filesystem Format
## from the Apps Filesystem `../apps/bin`
## and label it `NuttXBootVol`
genromfs
-f initrd
-d ../apps/bin
-V "NuttXBootVol"
## Put together a Padding with 64 KB of zeroes
head -c 65536 /dev/zero >/tmp/nuttx.zero
## Append Padding and Preliminary RAM Disk to NuttX Kernel
cat nuttx.bin /tmp/nuttx.zero initrd
>Picture
Subsequent we put together a Linux microSD for Ox64 as described in the previous article.
(Remember to flash OpenSBI and U-Boot Bootloader)
Then we do the Linux-To-NuttX Switcheroo: Overwrite the microSD Linux Picture by the NuttX Kernel…
## Overwrite the Linux Picture
## on Ox64 microSD
cp Picture
"/Volumes/NO NAME/Picture"
diskutil unmountDisk /dev/disk2
Insert the microSD into Ox64 and energy up Ox64.
Ox64 boots OpenSBI, which begins U-Boot Bootloader, which begins NuttX Kernel and the NuttX Shell (NSH).
What occurs after we press a key?
NuttX will reply to our keypress. (As a result of we configured the PLIC)
However the UART Enter reads as null proper now. (Pic above)