From 909980f4ebf49a4bef8b7be9ce7bcf17b7d9b9f7 Mon Sep 17 00:00:00 2001
From: Mark Poliakov <mark@alnyan.me>
Date: Tue, 21 Jan 2025 16:34:03 +0200
Subject: [PATCH] rv64: add jh7110/starfive visionfive2 support

---
 Cargo.lock                                   |   1 +
 etc/ld/riscv/riscv64-unknown-jh7110.ld       |  58 +++++
 etc/riscv64-unknown-none.json                |   1 +
 kernel/Cargo.toml                            |   3 +
 kernel/arch/riscv64/Cargo.toml               |   7 +
 kernel/arch/riscv64/src/context.rs           |   3 +
 kernel/arch/riscv64/src/mem/mod.rs           |  46 ++--
 kernel/arch/riscv64/src/mem/table.rs         |  16 +-
 kernel/lib/device-tree/src/driver/tree.rs    |  12 +-
 kernel/src/arch/riscv64/boot/entry.S         |  33 ++-
 kernel/src/arch/riscv64/exception.rs         |  52 +++--
 kernel/src/arch/riscv64/mod.rs               |  49 +++-
 kernel/src/arch/riscv64/timer.rs             |   2 +-
 kernel/src/device/interrupt/riscv_plic.rs    |  13 +-
 kernel/src/device/serial/mod.rs              |   2 +
 kernel/src/device/serial/ns16550a.rs         |   2 +
 kernel/src/device/serial/snps_dw_apb_uart.rs | 230 +++++++++++++++++++
 kernel/src/task/mod.rs                       |   1 +
 kernel/tools/gentables/src/riscv64.rs        |   2 +-
 xtask/src/build/cargo.rs                     |  17 +-
 xtask/src/build/mod.rs                       |  25 +-
 xtask/src/env.rs                             |  10 +-
 xtask/src/error.rs                           |   5 +
 xtask/src/qemu.rs                            |   7 +-
 24 files changed, 532 insertions(+), 65 deletions(-)
 create mode 100644 etc/ld/riscv/riscv64-unknown-jh7110.ld
 create mode 100644 kernel/src/device/serial/snps_dw_apb_uart.rs

diff --git a/Cargo.lock b/Cargo.lock
index b4beeaa3..e81d7701 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1024,6 +1024,7 @@ name = "kernel-arch-riscv64"
 version = "0.1.0"
 dependencies = [
  "bitflags 2.6.0",
+ "cfg-if",
  "device-api",
  "kernel-arch-interface",
  "libk-mm-interface",
diff --git a/etc/ld/riscv/riscv64-unknown-jh7110.ld b/etc/ld/riscv/riscv64-unknown-jh7110.ld
new file mode 100644
index 00000000..771efc0d
--- /dev/null
+++ b/etc/ld/riscv/riscv64-unknown-jh7110.ld
@@ -0,0 +1,58 @@
+ENTRY(__rv64_entry);
+
+KERNEL_PHYS_BASE = 0x40200000;
+KERNEL_VIRT_OFFSET = 0xFFFFFFF000000000;
+
+SECTIONS {
+    . = KERNEL_PHYS_BASE;
+    PROVIDE(__kernel_start = . + KERNEL_VIRT_OFFSET);
+
+    .text.entry : {
+        *(.text.entry)
+    }
+
+    . = ALIGN(16);
+    . = . + KERNEL_VIRT_OFFSET;
+
+    .text : AT(. - KERNEL_VIRT_OFFSET) {
+        KEEP(*(.text.vectors));
+        *(.text*)
+    }
+
+    . = ALIGN(4K);
+    .rodata : AT(. - KERNEL_VIRT_OFFSET) {
+        *(.rodata*)
+        *(.eh_frame*)
+    }
+
+    . = ALIGN(4K);
+    .data.tables : AT (. - KERNEL_VIRT_OFFSET) {
+        KEEP(*(.data.tables))
+    }
+
+    . = ALIGN(4K);
+    .data : AT(. - KERNEL_VIRT_OFFSET) {
+        *(.data*)
+        . = ALIGN(8);
+        /* PROVIDE(__global_pointer = . + 0x800 - KERNEL_VIRT_OFFSET); */
+
+        . = ALIGN(16);
+        PROVIDE(__init_array_start = .);
+        KEEP(*(.init_array*))
+        PROVIDE(__init_array_end = .);
+
+        *(.got*)
+    }
+
+    . = ALIGN(4K);
+    PROVIDE(__bss_start_phys = . - KERNEL_VIRT_OFFSET);
+    .bss : AT(. - KERNEL_VIRT_OFFSET) {
+        *(COMMON)
+        *(.bss*)
+    }
+    . = ALIGN(4K);
+    PROVIDE(__bss_end_phys = . - KERNEL_VIRT_OFFSET);
+    PROVIDE(__bss_size = __bss_end_phys - __bss_start_phys);
+
+    PROVIDE(__kernel_end = .);
+};
diff --git a/etc/riscv64-unknown-none.json b/etc/riscv64-unknown-none.json
index f8d0cec4..5a8d4d19 100644
--- a/etc/riscv64-unknown-none.json
+++ b/etc/riscv64-unknown-none.json
@@ -14,6 +14,7 @@
   "panic-strategy": "abort",
   "dynamic-linking": true,
   "relocation-model": "pic",
+  "code-model": "large",
   "eh-frame-header": false,
 
   "crt-objects-fallback": "false",
diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml
index 95ad7efc..ce2cbc59 100644
--- a/kernel/Cargo.toml
+++ b/kernel/Cargo.toml
@@ -95,5 +95,8 @@ fb_console = []
 aarch64_board_virt = ["kernel-arch-aarch64/aarch64_board_virt"]
 aarch64_board_raspi4b = ["kernel-arch-aarch64/aarch64_board_raspi4b"]
 
+riscv64_board_virt = ["kernel-arch-riscv64/riscv64_board_virt"]
+riscv64_board_jh7110 = ["kernel-arch-riscv64/riscv64_board_jh7110"]
+
 [lints]
 workspace = true
diff --git a/kernel/arch/riscv64/Cargo.toml b/kernel/arch/riscv64/Cargo.toml
index c78f8c87..11c51c16 100644
--- a/kernel/arch/riscv64/Cargo.toml
+++ b/kernel/arch/riscv64/Cargo.toml
@@ -14,6 +14,13 @@ tock-registers.workspace = true
 bitflags.workspace = true
 static_assertions.workspace = true
 log.workspace = true
+cfg-if.workspace = true
+
+[features]
+default = []
+riscv64_board_virt = []
+riscv64_board_jh7110 = []
+
 
 [lints]
 workspace = true
diff --git a/kernel/arch/riscv64/src/context.rs b/kernel/arch/riscv64/src/context.rs
index 0b900a47..9761df87 100644
--- a/kernel/arch/riscv64/src/context.rs
+++ b/kernel/arch/riscv64/src/context.rs
@@ -150,6 +150,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
     unsafe fn enter(&self) -> ! {
         unsafe {
             self.load_state();
+            mem::tlb_flush_full();
             __rv64_enter_task(self.inner.get())
         }
     }
@@ -162,6 +163,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
         unsafe {
             from.store_state();
             self.load_state();
+            mem::tlb_flush_full();
             __rv64_switch_task(self.inner.get(), from.inner.get())
         }
     }
@@ -169,6 +171,7 @@ impl<K: KernelTableManager, PA: PhysicalMemoryAllocator<Address = PhysicalAddres
     unsafe fn switch_and_drop(&self, thread: *const ()) {
         unsafe {
             self.load_state();
+            mem::tlb_flush_full();
             __rv64_switch_task_and_drop(self.inner.get(), thread)
         }
     }
diff --git a/kernel/arch/riscv64/src/mem/mod.rs b/kernel/arch/riscv64/src/mem/mod.rs
index 26217a01..101a0557 100644
--- a/kernel/arch/riscv64/src/mem/mod.rs
+++ b/kernel/arch/riscv64/src/mem/mod.rs
@@ -1,10 +1,11 @@
+use cfg_if::cfg_if;
 use kernel_arch_interface::{
     mem::{DeviceMemoryAttributes, KernelTableManager, RawDeviceMemoryMapping},
     split_spinlock,
 };
 use libk_mm_interface::{
     address::PhysicalAddress,
-    table::{page_align_down, page_index, EntryLevel, EntryLevelExt},
+    table::{page_index, EntryLevel, EntryLevelExt},
 };
 use memtables::riscv64::PageAttributes;
 use static_assertions::{const_assert, const_assert_eq};
@@ -30,14 +31,22 @@ split_spinlock! {
         unsafe { KernelImageObject::new(FixedTables::zeroed()) };
 }
 
+cfg_if! {
+    if #[cfg(feature = "riscv64_board_virt")] {
+        pub const KERNEL_PHYS_BASE: usize = 0x80200000;
+    } else if #[cfg(feature = "riscv64_board_jh7110")] {
+        pub const KERNEL_PHYS_BASE: usize = 0x40200000;
+    } else if #[cfg(rust_analyzer)] {
+        pub const KERNEL_PHYS_BASE: usize = 0x80200000;
+    }
+}
+
 pub const KERNEL_VIRT_OFFSET: usize = kernel_arch_interface::KERNEL_VIRT_OFFSET;
-pub const KERNEL_PHYS_BASE: usize = 0x80000000;
 pub const SIGN_EXTEND_MASK: usize = 0xFFFFFF80_00000000;
 
 pub const KERNEL_START_L1I: usize = page_index::<L1>(KERNEL_VIRT_OFFSET + KERNEL_PHYS_BASE);
 pub const KERNEL_L2I: usize = page_index::<L2>(KERNEL_VIRT_OFFSET + KERNEL_PHYS_BASE);
-const_assert_eq!(KERNEL_START_L1I, 450);
-const_assert_eq!(KERNEL_L2I, 0);
+const_assert_eq!(KERNEL_L2I, 1);
 
 // Runtime mappings
 // 1GiB of device memory space
@@ -120,8 +129,8 @@ unsafe fn map_device_memory_l3(
                 DEVICE_MAPPING_L3S[l2i][l3i] =
                     PageEntry::page(base.add(j * L3::SIZE), PageAttributes::W);
             }
-            tlb_flush_va(DEVICE_MAPPING_OFFSET + l2i * L2::SIZE + l3i * L3::SIZE);
         }
+        tlb_flush_full();
 
         return Ok(DEVICE_MAPPING_OFFSET + i * L3::SIZE);
     }
@@ -148,9 +157,10 @@ unsafe fn map_device_memory_l2(
             for j in 0..count {
                 DEVICE_MAPPING_L2[i + j] =
                     PageEntry::<L2>::block(base.add(j * L2::SIZE), PageAttributes::W);
-                // tlb_flush_vaae1(DEVICE_MAPPING_OFFSET + (i + j) * L2::SIZE);
+                // tlb_flush_va(DEVICE_MAPPING_OFFSET + (i + j) * L2::SIZE);
             }
         }
+        tlb_flush_full();
 
         return Ok(DEVICE_MAPPING_OFFSET + i * L2::SIZE);
     }
@@ -212,7 +222,7 @@ pub(crate) unsafe fn unmap_device_memory(map: &RawDeviceMemoryMapping<KernelTabl
                     DEVICE_MAPPING_L3S[l2i][l3i] = PageEntry::INVALID;
                 }
 
-                // tlb_flush_vaae1(page);
+                tlb_flush_va(page);
             }
         }
         L2::SIZE => todo!(),
@@ -236,7 +246,7 @@ pub fn auto_address<T>(x: *const T) -> usize {
 /// Only meant to be called once per each HART during their early init.
 pub unsafe fn enable_mmu() {
     let l1_phys = auto_address(&raw const KERNEL_TABLES) as u64;
-
+    tlb_flush_full();
     SATP.write(SATP::PPN.val(l1_phys >> 12) + SATP::MODE::Sv39);
 }
 
@@ -249,7 +259,7 @@ pub unsafe fn unmap_lower_half() {
     let mut tables = KERNEL_TABLES.lock();
     let kernel_l1i_lower = page_index::<L1>(KERNEL_PHYS_BASE);
     tables.l1.data[kernel_l1i_lower] = 0;
-    tlb_flush_va(page_align_down::<L1>(KERNEL_PHYS_BASE));
+    tlb_flush_full();
 }
 
 /// Sets up run-time kernel translation tables.
@@ -276,15 +286,25 @@ pub unsafe fn setup_fixed_tables() {
     assert_eq!(tables.l1.data[DEVICE_MAPPING_L1I], 0);
     tables.l1.data[DEVICE_MAPPING_L1I] =
         ((device_mapping_l2_phys as u64) >> 2) | PageAttributes::V.bits();
-    // tlb_flush_vaae1(DEVICE_MAPPING_OFFSET);
 
     for l1i in 0..RAM_MAPPING_L1_COUNT {
         let physical = (l1i as u64) << L1::SHIFT;
-        tables.l1.data[l1i + RAM_MAPPING_START_L1I] =
-            (physical >> 2) | (PageAttributes::R | PageAttributes::W | PageAttributes::V).bits();
+        tables.l1.data[l1i + RAM_MAPPING_START_L1I] = (physical >> 2)
+            | (PageAttributes::R
+                | PageAttributes::W
+                | PageAttributes::A
+                | PageAttributes::D
+                | PageAttributes::V)
+                .bits();
     }
 
-    // tlb_flush_all()
+    tlb_flush_full();
+}
+
+pub fn tlb_flush_full() {
+    unsafe {
+        core::arch::asm!("sfence.vma");
+    }
 }
 
 pub fn tlb_flush_va(va: usize) {
diff --git a/kernel/arch/riscv64/src/mem/table.rs b/kernel/arch/riscv64/src/mem/table.rs
index 8a807441..1be91b4a 100644
--- a/kernel/arch/riscv64/src/mem/table.rs
+++ b/kernel/arch/riscv64/src/mem/table.rs
@@ -130,7 +130,13 @@ impl<L: NonTerminalEntryLevel> PageEntry<L> {
     pub fn block(address: PhysicalAddress, attrs: PageAttributes) -> Self {
         // TODO validate address alignment
         Self(
-            (address.into_u64() >> 2) | (PageAttributes::R | PageAttributes::V | attrs).bits(),
+            (address.into_u64() >> 2)
+                | (PageAttributes::R
+                    | PageAttributes::A
+                    | PageAttributes::D
+                    | PageAttributes::V
+                    | attrs)
+                    .bits(),
             PhantomData,
         )
     }
@@ -156,7 +162,13 @@ impl<L: NonTerminalEntryLevel> PageEntry<L> {
 impl PageEntry<L3> {
     pub fn page(address: PhysicalAddress, attrs: PageAttributes) -> Self {
         Self(
-            (address.into_u64() >> 2) | (PageAttributes::R | PageAttributes::V | attrs).bits(),
+            (address.into_u64() >> 2)
+                | (PageAttributes::R
+                    | PageAttributes::A
+                    | PageAttributes::D
+                    | PageAttributes::V
+                    | attrs)
+                    .bits(),
             PhantomData,
         )
     }
diff --git a/kernel/lib/device-tree/src/driver/tree.rs b/kernel/lib/device-tree/src/driver/tree.rs
index 34c9b78a..8e2103c5 100644
--- a/kernel/lib/device-tree/src/driver/tree.rs
+++ b/kernel/lib/device-tree/src/driver/tree.rs
@@ -105,7 +105,11 @@ impl Node {
         let inner = self.device.or_init_with_opt(|| {
             let compatible = self.compatible?;
             let drivers = DRIVERS.read();
-            let driver = drivers.iter().find(|d| d.matches(compatible))?;
+            let driver = drivers.iter().find(|d| d.matches(compatible));
+            if driver.is_none() {
+                // log::warn!("No driver for {compatible:?}");
+            }
+            let driver = driver?;
 
             let device = driver.imp.probe(&self, &cx);
 
@@ -210,7 +214,11 @@ impl Node {
     /// * `Err(Error::DoesNotExist)` - couldn't find a device/driver for this node.
     /// * `Err(other)` - initialization failed.
     pub fn force_init(self: Arc<Self>) -> Result<(), Error> {
-        let device = self.clone().probe().ok_or(Error::DoesNotExist)?;
+        let device = self
+            .clone()
+            .probe()
+            .ok_or(Error::DoesNotExist)
+            .inspect_err(|_| log::error!("Does not exist: probe({:?})", self.name))?;
 
         self.init_token.try_init_with_opt(|| {
             unsafe { device.init() }?;
diff --git a/kernel/src/arch/riscv64/boot/entry.S b/kernel/src/arch/riscv64/boot/entry.S
index 40246a1c..8150d5a1 100644
--- a/kernel/src/arch/riscv64/boot/entry.S
+++ b/kernel/src/arch/riscv64/boot/entry.S
@@ -6,13 +6,41 @@
 .endm
 
 .pushsection .text.entry
-.option norvc
+
+.option push
+.option rvc
 
 .global __rv64_entry
+.global __boot_header
 .global __rv64_secondary_entry
 
 .type __rv64_entry, @function
+.type __boot_header, @object
+__boot_header:
 __rv64_entry:
+    // Look <s>ma</s>u-boot, I'm Linux!
+    .ascii  "MZ"                // Magic 0
+    j       __rv64_real_entry   // Jump to real entry (if entered by non-Linux bootloader)
+    .long   0
+    .quad   0x200000            // Offset from RAM start
+    .quad   2000000             // Image size TODO fill this by post-tooling
+    .quad   0                   // Kernel flags
+    .long   0x2                 // Header version
+    .long   0
+    .quad   0
+    .ascii  "RISCV\x00\x00\x00" // Magic 1
+    .ascii  "RSC\x05"           // Magic 2
+    .long 0
+.size __rv64_entry, . - __rv64_entry
+.size __boot_header, . - __boot_header
+
+.option pop
+
+.option push
+.option norvc
+
+.type __rv64_real_entry, @function
+__rv64_real_entry:
     // a0 - bootstrap HART ID
     // a1 - device tree blob
     // mhartid == a0
@@ -34,7 +62,7 @@ __rv64_entry:
     LOAD_PCREL .L03, t0, {entry_smode_lower} - {kernel_virt_offset}
 
     jr t0
-.size __rv64_entry, . - __rv64_entry
+.size __rv64_real_entry, . - __rv64_real_entry
 
 .type __rv64_secondary_entry, @function
 __rv64_secondary_entry:
@@ -50,4 +78,5 @@ __rv64_secondary_entry:
     jr t0
 .size __rv64_secondary_entry, . - __rv64_secondary_entry
 
+.option pop
 .popsection
diff --git a/kernel/src/arch/riscv64/exception.rs b/kernel/src/arch/riscv64/exception.rs
index c88f619d..f5ab2d47 100644
--- a/kernel/src/arch/riscv64/exception.rs
+++ b/kernel/src/arch/riscv64/exception.rs
@@ -1,11 +1,16 @@
 use core::arch::global_asm;
 
-use abi::{arch::SavedFrame, primitive_enum, process::Signal, SyscallFunction};
+use abi::{
+    arch::SavedFrame,
+    primitive_enum,
+    process::{ExitCode, Signal},
+    SyscallFunction,
+};
 use kernel_arch::task::TaskFrame;
 use libk::{device::external_interrupt_controller, task::thread::Thread};
 use tock_registers::interfaces::ReadWriteable;
 
-use kernel_arch_riscv64::registers::STVEC;
+use kernel_arch_riscv64::{mem, registers::STVEC};
 
 use crate::syscall;
 
@@ -55,7 +60,8 @@ pub fn init_smode_exceptions() {
     let address = (&raw const __rv64_smode_trap_vectors).addr();
 
     STVEC.set_base(address);
-    STVEC.modify(STVEC::MODE::Vectored);
+    STVEC.modify(STVEC::MODE::Direct);
+    // STVEC.modify(STVEC::MODE::Vectored);
 }
 
 unsafe fn umode_exception_handler(frame: &mut TrapFrame) {
@@ -63,28 +69,15 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) {
 
     let cause = Cause::try_from(frame.scause).ok();
 
-    let dump = match cause {
+    let (dump, dump_tval) = match cause {
         Some(Cause::LoadPageFault)
         | Some(Cause::StorePageFault)
         | Some(Cause::LoadAccessFault)
         | Some(Cause::StoreAccessFault)
         | Some(Cause::InstructionPageFault)
         | Some(Cause::InstructionAccessFault) => {
-            let translation = if let Some(space) = thread.try_get_process().map(|p| p.space()) {
-                space.translate(frame.stval as usize).ok()
-            } else {
-                None
-            };
-
             thread.raise_signal(Signal::MemoryAccessViolation);
-
-            if let Some(physical) = translation {
-                log::warn!(" * tval translates to {physical:#x}");
-            } else {
-                log::warn!(" * tval does not translate");
-            }
-
-            true
+            (true, true)
         }
         Some(Cause::EcallUmode) => {
             let func = frame.an[0];
@@ -94,17 +87,19 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) {
 
             let args = &frame.an[1..];
             let result = syscall::raw_syscall_handler(func, args) as _;
+            mem::tlb_flush_full();
             frame.an[0] = result;
             frame.sepc += 4;
-            false
+            (false, false)
         }
         _ => {
             thread.raise_signal(Signal::MemoryAccessViolation);
-            true
+            (true, false)
         }
     };
 
     if dump {
+        let process = thread.process();
         log::warn!(
             "U-mode exception cause={:?} ({}), epc={:#x}, sp={:#x}, tval={:#x}",
             cause,
@@ -113,6 +108,20 @@ unsafe fn umode_exception_handler(frame: &mut TrapFrame) {
             frame.sp,
             frame.stval
         );
+        log::warn!("In thread {} ({:?})", thread.id, *thread.name.read());
+        log::warn!("Of process {} ({:?})", process.id, process.name);
+
+        if dump_tval {
+            let translation = process.space().translate(frame.stval as usize).ok();
+
+            if let Some(physical) = translation {
+                log::warn!(" * tval translates to {physical:#x}");
+            } else {
+                log::warn!(" * tval does not translate");
+            }
+        }
+
+        thread.exit_process(ExitCode::BySignal(Ok(Signal::MemoryAccessViolation)));
     }
 }
 
@@ -169,7 +178,7 @@ unsafe extern "C" fn smode_interrupt_handler(frame: *mut TrapFrame) {
                 intc.handle_pending_irqs();
             }
         }
-        n => todo!("Unhandled interrupt #{n}"),
+        n => log::warn!("Unhandled interrupt #{n}"),
     }
 
     if !smode && let Some(thread) = Thread::get_current() {
@@ -192,6 +201,7 @@ unsafe extern "C" fn smode_general_trap_handler(frame: *mut TrapFrame) {
     if !smode && let Some(thread) = Thread::get_current() {
         thread.handle_pending_signals(frame);
     }
+    mem::tlb_flush_full();
 }
 
 impl TaskFrame for TrapFrame {
diff --git a/kernel/src/arch/riscv64/mod.rs b/kernel/src/arch/riscv64/mod.rs
index e297d5f5..8ae36b0d 100644
--- a/kernel/src/arch/riscv64/mod.rs
+++ b/kernel/src/arch/riscv64/mod.rs
@@ -1,5 +1,5 @@
 #![allow(missing_docs)]
-use core::sync::atomic::{self, AtomicU32, Ordering};
+use core::sync::atomic::{self, AtomicBool, AtomicU32, Ordering};
 
 use abi::error::Error;
 use alloc::sync::Arc;
@@ -44,11 +44,13 @@ pub static BOOT_HART_ID: AtomicU32 = AtomicU32::new(u32::MAX);
 pub struct Riscv64 {
     dt: OneTimeInit<DeviceTree<'static>>,
     initrd: OneTimeInit<PhysicalRef<'static, [u8]>>,
+    smp: AtomicBool,
 }
 
 pub static PLATFORM: Riscv64 = Riscv64 {
     dt: OneTimeInit::new(),
     initrd: OneTimeInit::new(),
+    smp: AtomicBool::new(true),
 };
 
 #[derive(Debug, Clone, Copy)]
@@ -67,14 +69,22 @@ impl Platform for Riscv64 {
     }
 
     unsafe fn send_ipi(&self, target: IpiDeliveryTarget, msg: IpiMessage) -> Result<bool, Error> {
-        smp::send_ipi(target, msg)?;
-        Ok(true)
+        if self.smp.load(Ordering::Acquire) {
+            smp::send_ipi(target, msg)?;
+            Ok(true)
+        } else {
+            Ok(false)
+        }
     }
 
     unsafe fn start_application_processors(&self) {
-        let dt = self.dt.get();
-        if let Err(error) = smp::start_secondary_harts(dt) {
-            log::error!("Couldn't start secondary harts: {error:?}");
+        // TODO asymmetric systems with different hart types are not yet supported.
+        // e.g., in JH7110 there're two different types of cores
+        if self.smp.load(Ordering::Acquire) {
+            let dt = self.dt.get();
+            if let Err(error) = smp::start_secondary_harts(dt) {
+                log::error!("Couldn't start secondary harts: {error:?}");
+            }
         }
     }
 
@@ -194,16 +204,22 @@ impl Riscv64 {
             // Create device tree sysfs nodes
             device_tree::util::create_sysfs_nodes(dt);
 
-            let (_, machine_name) = Self::machine_name(dt);
+            let (machine_compatible, machine_name) = Self::machine_name(dt);
 
             unflatten_device_tree(dt);
 
+            if let Some(machine_compatible) = machine_compatible {
+                Self::apply_board_workarounds(machine_compatible);
+            }
+
             if let Err(error) = Self::setup_clock_timebase(dt) {
                 log::error!("Could not setup clock timebase from device tree: {error:?}");
             }
-            Self::setup_chosen_stdout(dt).ok();
-
-            libk::debug::disable_early_sinks();
+            if let Err(error) = Self::setup_chosen_stdout(dt) {
+                log::error!("chosen-stdout setup error: {error:?}");
+            } else {
+                libk::debug::disable_early_sinks();
+            }
 
             if let Some(machine) = machine_name {
                 log::info!("Running on {machine:?}");
@@ -241,6 +257,17 @@ impl Riscv64 {
         Ok(())
     }
 
+    fn apply_board_workarounds(compatible: &str) {
+        #[allow(clippy::single_match)]
+        match compatible {
+            "starfive,visionfive-2-v1.3b" => {
+                log::warn!("VisionFive 2: disabling SMP, OS doesn't yet handle the HARTs properly");
+                PLATFORM.smp.store(false, Ordering::Release);
+            }
+            _ => (),
+        }
+    }
+
     fn machine_name(dt: &'static DeviceTree) -> (Option<&'static str>, Option<&'static str>) {
         (
             dt.root().prop_string("compatible"),
@@ -254,6 +281,7 @@ impl Riscv64 {
             .prop_cell("timebase-frequency", 1)
             .ok_or(Error::DoesNotExist)?;
         timer::FREQUENCY.store(timebase_frequency, Ordering::Release);
+        log::info!("System timer frequency: {timebase_frequency}");
         Ok(())
     }
 
@@ -266,6 +294,7 @@ impl Riscv64 {
         let node = stdout_path.and_then(device_tree::driver::find_node);
 
         if let Some(node) = node {
+            log::info!("Probe chosen stdout: {:?}", node.name());
             node.force_init()?;
         }
 
diff --git a/kernel/src/arch/riscv64/timer.rs b/kernel/src/arch/riscv64/timer.rs
index 0e508c27..4d4cf07c 100644
--- a/kernel/src/arch/riscv64/timer.rs
+++ b/kernel/src/arch/riscv64/timer.rs
@@ -46,7 +46,7 @@ pub fn init_hart(is_bsp: bool) {
         if is_bsp {
             LAST_TICK.store(now, Ordering::Release);
         }
-        sbi::sbi_set_timer(now + frequency / TICK_RATE);
+        sbi::sbi_set_timer(now.wrapping_add(frequency / TICK_RATE));
     } else {
         SIE.modify(SIE::STIE::CLEAR);
         sbi::sbi_set_timer(u64::MAX);
diff --git a/kernel/src/device/interrupt/riscv_plic.rs b/kernel/src/device/interrupt/riscv_plic.rs
index b5e2a2cd..a67f5cf0 100644
--- a/kernel/src/device/interrupt/riscv_plic.rs
+++ b/kernel/src/device/interrupt/riscv_plic.rs
@@ -82,7 +82,7 @@ struct Context {
     enable: IrqSafeRwLock<DeviceMemoryIo<'static, ContextEnableRegs>>,
     control: IrqSafeRwLock<DeviceMemoryIo<'static, ContextControlRegs>>,
     // TODO scale the table depending on effective MAX_IRQS value
-    table: IrqSafeRwLock<FixedInterruptTable<32>>,
+    table: IrqSafeRwLock<FixedInterruptTable<64>>,
 }
 
 struct Inner {
@@ -112,12 +112,15 @@ impl Plic {
 
     fn validate_irq(&self, irq: Irq) -> Result<u32, Error> {
         let Irq::External(irq) = irq else {
+            log::error!("plic: irq {irq:?} is not an external interrupt");
             return Err(Error::InvalidArgument);
         };
         if irq == 0 {
+            log::error!("plic: irq cannot be zero");
             return Err(Error::InvalidArgument);
         }
         if irq as usize >= self.max_irqs {
+            log::error!("plic: irq ({}) >= max_irqs ({})", irq, self.max_irqs);
             return Err(Error::InvalidArgument);
         }
         Ok(irq)
@@ -138,7 +141,8 @@ impl ExternalInterruptController for Plic {
         let bsp_hart_id = BOOT_HART_ID.load(Ordering::Acquire);
         let context = self
             .hart_context(bsp_hart_id)
-            .ok_or(Error::InvalidArgument)?
+            .ok_or(Error::InvalidArgument)
+            .inspect_err(|_| log::error!("plic: no context for hart {bsp_hart_id}"))?
             .context
             .get();
 
@@ -159,7 +163,8 @@ impl ExternalInterruptController for Plic {
         let irq = self.validate_irq(irq)?;
         let context = self
             .hart_context(bsp_hart_id)
-            .ok_or(Error::InvalidArgument)?
+            .ok_or(Error::InvalidArgument)
+            .inspect_err(|_| log::error!("plic: no context for hart {bsp_hart_id}"))?
             .context
             .get();
         let mut table = context.table.write();
@@ -277,7 +282,7 @@ fn map_context_to_hart(target: u32) -> Option<u32> {
 }
 
 device_tree_driver! {
-    compatible: ["sifive,plic-1.0.0", "riscv,plic0"],
+    compatible: ["starfive,jh7110-plic", "sifive,plic-1.0.0", "riscv,plic0"],
     driver: {
         fn probe(&self, node: &Arc<Node>, context: &ProbeContext) -> Option<Arc<dyn Device>> {
             let base = node.map_base(context, 0)?;
diff --git a/kernel/src/device/serial/mod.rs b/kernel/src/device/serial/mod.rs
index 34ef1f06..e9d3db13 100644
--- a/kernel/src/device/serial/mod.rs
+++ b/kernel/src/device/serial/mod.rs
@@ -8,3 +8,5 @@ pub mod pl011;
 
 #[cfg(any(target_arch = "riscv64", rust_analyzer))]
 pub mod ns16550a;
+#[cfg(any(target_arch = "riscv64", rust_analyzer))]
+pub mod snps_dw_apb_uart;
diff --git a/kernel/src/device/serial/ns16550a.rs b/kernel/src/device/serial/ns16550a.rs
index b7feee8d..1e7ce1fd 100644
--- a/kernel/src/device/serial/ns16550a.rs
+++ b/kernel/src/device/serial/ns16550a.rs
@@ -109,6 +109,7 @@ impl Io {
 
 impl Device for Ns16550a {
     unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
+        log::info!("Init ns16550a @ {:#x}", self.base);
         let mut io = Io {
             regs: DeviceMemoryIo::map(self.base, Default::default())?,
         };
@@ -191,6 +192,7 @@ device_tree_driver!(
     driver: {
         fn probe(&self, node: &Arc<Node>, context: &ProbeContext) -> Option<Arc<dyn Device>> {
             let base = node.map_base(context, 0)?;
+            log::debug!("ns16550a base = {base:#x}");
             let irq = node.interrupt(0)?;
 
             Some(Arc::new(Ns16550a {
diff --git a/kernel/src/device/serial/snps_dw_apb_uart.rs b/kernel/src/device/serial/snps_dw_apb_uart.rs
new file mode 100644
index 00000000..4951124e
--- /dev/null
+++ b/kernel/src/device/serial/snps_dw_apb_uart.rs
@@ -0,0 +1,230 @@
+//! Synopsys DesignWare 8250 driver
+use abi::{error::Error, io::TerminalOptions};
+use alloc::sync::Arc;
+use device_api::{
+    device::Device,
+    interrupt::{FullIrq, InterruptHandler},
+};
+use device_tree::driver::{device_tree_driver, Node, ProbeContext};
+use libk::{
+    debug::DebugSink,
+    device::{external_interrupt_controller, manager::DEVICE_REGISTRY},
+    vfs::{Terminal, TerminalInput, TerminalOutput},
+};
+use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIo};
+use libk_util::{sync::IrqSafeSpinlock, OneTimeInit};
+use tock_registers::{
+    interfaces::{ReadWriteable, Readable, Writeable},
+    register_bitfields, register_structs,
+    registers::{ReadOnly, ReadWrite, WriteOnly},
+};
+
+register_bitfields! {
+    u32,
+    IER [
+        PTIME OFFSET(7) NUMBITS(1) [],
+        EDSSI OFFSET(3) NUMBITS(1) [],
+        ELSI  OFFSET(2) NUMBITS(1) [],
+        // Transmit buffer available
+        ETBEI OFFSET(1) NUMBITS(1) [],
+        // Receive data available
+        ERBFI OFFSET(0) NUMBITS(1) [],
+    ],
+    LSR [
+        // Data ready bit
+        DR OFFSET(0) NUMBITS(1) [],
+        // Transmitter holding register empty
+        THRE OFFSET(5) NUMBITS(1) [],
+    ]
+}
+
+register_structs! {
+    #[allow(non_snake_case)]
+    Regs {
+        // DLAB=0, Write: transmitter holding register/Read: receiver buffer register
+        // DLAB=1, Read/Write: divisor latch low
+        (0x000 => DR: ReadWrite<u32>),
+        // DLAB=0: Interrupt enable register
+        // DLAB=1: Divisor latch high
+        (0x004 => IER: ReadWrite<u32, IER::Register>),
+        // Read: interrupt identification register/Write: frame control register
+        (0x008 => IIR: ReadWrite<u32>),
+        // Line control register
+        (0x00C => LCR: ReadWrite<u32>),
+        // Modem control register
+        (0x010 => MCR: ReadWrite<u32>),
+        // Line status register
+        (0x014 => LSR: ReadOnly<u32, LSR::Register>),
+        // Modem status register
+        (0x018 => MSR: ReadOnly<u32>),
+        // Scratchpad
+        (0x01C => SCR: ReadWrite<u32>),
+        // Low-power divisor latch low
+        (0x020 => LPDLL: ReadWrite<u32>),
+        // Low-power divisor latch high
+        (0x024 => LPDLH: ReadWrite<u32>),
+        (0x028 => _0),
+        // Shadow receive/transmit buffer
+        (0x030 => SDR: [ReadWrite<u32>; 16]),
+        (0x070 => FAR: ReadWrite<u32>),
+        (0x074 => TFR: ReadOnly<u32>),
+        (0x078 => RFW: WriteOnly<u32>),
+        (0x07C => USR: ReadOnly<u32>),
+        (0x080 => TFL: ReadOnly<u32>),
+        (0x084 => RFL: ReadOnly<u32>),
+        (0x088 => SRR: WriteOnly<u32>),
+        (0x08C => SRTS: ReadWrite<u32>),
+        (0x090 => SBCR: ReadWrite<u32>),
+        (0x094 => SDMAM: ReadWrite<u32>),
+        (0x098 => SFE: ReadWrite<u32>),
+        (0x09C => SRT: ReadWrite<u32>),
+        (0x0A0 => STET: ReadWrite<u32>),
+        (0x0A4 => HTX: ReadWrite<u32>),
+        (0x0A8 => DMASA: WriteOnly<u32>),
+        (0x0AC => _1),
+        (0x0F4 => CPR: ReadOnly<u32>),
+        (0x0F8 => UCV: ReadOnly<u32>),
+        (0x0FC => CTR: ReadOnly<u32>),
+        (0x100 => @END),
+    }
+}
+
+struct Io {
+    regs: DeviceMemoryIo<'static, Regs>,
+}
+
+struct Inner {
+    io: IrqSafeSpinlock<Io>,
+}
+
+/// Synopsys DesignWare 8250 UART
+pub struct DwUart {
+    base: PhysicalAddress,
+    irq: FullIrq,
+    inner: OneTimeInit<Arc<Terminal<Inner>>>,
+}
+
+impl Io {
+    fn send(&mut self, byte: u8) {
+        // TODO
+        if byte == b'\n' {
+            self.send(b'\r');
+        }
+
+        while !self.regs.LSR.matches_all(LSR::THRE::SET) {
+            core::hint::spin_loop();
+        }
+        self.regs.DR.set(byte as u32);
+    }
+
+    fn init(&mut self) {
+        self.regs.IER.set(0);
+    }
+
+    fn handle_irq(&mut self) -> Option<u8> {
+        let status = self.regs.IIR.get();
+
+        if status & 0xF == 4 {
+            Some(self.regs.DR.get() as u8)
+        } else {
+            None
+        }
+    }
+}
+
+impl InterruptHandler for DwUart {
+    fn handle_irq(self: Arc<Self>, _vector: Option<usize>) -> bool {
+        let inner = self.inner.get();
+        let output = inner.output();
+        let byte = output.io.lock().handle_irq();
+
+        if let Some(byte) = byte {
+            inner.write_to_input(byte);
+            true
+        } else {
+            false
+        }
+    }
+}
+
+impl Device for DwUart {
+    unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
+        let regs = DeviceMemoryIo::map(self.base, Default::default())?;
+        let mut io = Io { regs };
+        io.init();
+
+        let input = TerminalInput::with_capacity(64)?;
+        let output = Inner {
+            io: IrqSafeSpinlock::new(io),
+        };
+
+        let terminal = self.inner.init(Arc::new(Terminal::from_parts(
+            TerminalOptions::const_default(),
+            input,
+            output,
+        )));
+
+        DEVICE_REGISTRY
+            .serial_terminal
+            .register(terminal.clone(), Some(self.clone()))
+            .ok();
+
+        Ok(())
+    }
+
+    unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
+        let intc = external_interrupt_controller()?;
+        intc.register_irq(self.irq.irq, Default::default(), self.clone())?;
+        intc.enable_irq(self.irq.irq)?;
+
+        let output = self.inner.get().output();
+        let io = output.io.lock();
+        io.regs.IER.modify(IER::ERBFI::SET);
+        Ok(())
+    }
+
+    fn display_name(&self) -> &str {
+        "Synopsys DesignWare 8250 UART"
+    }
+}
+
+impl DebugSink for DwUart {
+    fn putc(&self, c: u8) -> Result<(), Error> {
+        self.inner.get().putc_to_output(c)
+    }
+
+    fn supports_control_sequences(&self) -> bool {
+        true
+    }
+}
+
+impl TerminalOutput for Inner {
+    fn write(&self, byte: u8) -> Result<(), Error> {
+        self.io.lock().send(byte);
+        Ok(())
+    }
+
+    fn write_multiple(&self, bytes: &[u8]) -> Result<usize, Error> {
+        let mut lock = self.io.lock();
+        for &byte in bytes {
+            lock.send(byte);
+        }
+        Ok(bytes.len())
+    }
+}
+
+device_tree_driver! {
+    compatible: ["snps,dw-apb-uart"],
+    driver: {
+        fn probe(&self, node: &Arc<Node>, context: &ProbeContext) -> Option<Arc<dyn Device>> {
+            let base = node.map_base(context, 0)?;
+            let irq = node.interrupt(0)?;
+
+            Some(Arc::new(DwUart {
+                base,
+                irq,
+                inner: OneTimeInit::new()
+            }))
+        }
+    }
+}
diff --git a/kernel/src/task/mod.rs b/kernel/src/task/mod.rs
index 5145dd63..3beeefbf 100644
--- a/kernel/src/task/mod.rs
+++ b/kernel/src/task/mod.rs
@@ -73,6 +73,7 @@ pub unsafe fn enter() -> ! {
         AP_CAN_ENTER.signal();
     }
 
+    log::info!("Start queue_index={}", cpu.queue_index());
     let queue = CpuQueue::for_cpu(cpu.queue_index());
     cpu.set_scheduler(queue);
 
diff --git a/kernel/tools/gentables/src/riscv64.rs b/kernel/tools/gentables/src/riscv64.rs
index a6fa185f..2c4bdafd 100644
--- a/kernel/tools/gentables/src/riscv64.rs
+++ b/kernel/tools/gentables/src/riscv64.rs
@@ -32,7 +32,7 @@ const L3_SHIFT: usize = 12;
 const L3_PAGE_SIZE: usize = 1 << L3_SHIFT;
 
 fn segment_attributes(f: u32) -> PageAttributes {
-    let mut attrs = PageAttributes::R;
+    let mut attrs = PageAttributes::R | PageAttributes::A | PageAttributes::D;
     if f & PF_W != 0 {
         attrs |= PageAttributes::W;
     }
diff --git a/xtask/src/build/cargo.rs b/xtask/src/build/cargo.rs
index bc964525..9e12c0c2 100644
--- a/xtask/src/build/cargo.rs
+++ b/xtask/src/build/cargo.rs
@@ -152,9 +152,20 @@ impl<'e> CargoBuilder<'e> {
                     "-Zunstable-options",
                 ]);
 
-                match env.board {
-                    Board::virt | Board::default => command.arg("--features=aarch64_board_virt"),
-                    Board::raspi4b => command.arg("--features=aarch64_board_raspi4b"),
+                match (env.arch, env.board) {
+                    (Arch::aarch64, Board::virt | Board::default) => {
+                        command.arg("--features=aarch64_board_virt");
+                    }
+                    (Arch::aarch64, Board::raspi4b) => {
+                        command.arg("--features=aarch64_board_raspi4b");
+                    }
+                    (Arch::riscv64, Board::virt | Board::default) => {
+                        command.arg("--features=riscv64_board_virt");
+                    }
+                    (Arch::riscv64, Board::jh7110) => {
+                        command.arg("--features=riscv64_board_jh7110");
+                    }
+                    (_, _) => (),
                 };
 
                 if env.profile == Profile::Release {
diff --git a/xtask/src/build/mod.rs b/xtask/src/build/mod.rs
index 1ad8a4f0..fd8f9a6e 100644
--- a/xtask/src/build/mod.rs
+++ b/xtask/src/build/mod.rs
@@ -39,10 +39,11 @@ impl CheckAction {
 pub struct ToolsBuilt(pub PathBuf);
 pub struct KernelBuilt(pub PathBuf);
 pub struct KernelProcessed(pub KernelBuilt);
+pub struct KernelBin(pub PathBuf);
 pub struct InitrdGenerated(pub PathBuf);
 pub struct ImageBuilt(pub PathBuf);
 pub enum AllBuilt {
-    Riscv64(KernelProcessed, InitrdGenerated),
+    Riscv64(KernelBin, InitrdGenerated),
     X86_64(ImageBuilt),
     AArch64(KernelProcessed, InitrdGenerated),
     I686(ImageBuilt),
@@ -62,6 +63,26 @@ pub fn build_kernel(env: &BuildEnv, _: AllOk) -> Result<KernelBuilt, Error> {
     Ok(KernelBuilt(env.kernel_output_dir.join("yggdrasil-kernel")))
 }
 
+pub fn make_kernel_bin(
+    env: &BuildEnv,
+    kernel: KernelProcessed,
+    _: AllOk,
+) -> Result<KernelBin, Error> {
+    log::info!("Building yggdrasil-kernel.bin");
+    let kernel_bin = env.kernel_output_dir.join("yggdrasil-kernel.bin");
+
+    let status = Command::new("llvm-objcopy")
+        .args(["-O", "binary"])
+        .arg(kernel.0 .0)
+        .arg(&kernel_bin)
+        .status()?;
+    if !status.success() {
+        return Err(Error::ExternalCommandFailed);
+    }
+
+    Ok(KernelBin(kernel_bin))
+}
+
 pub fn generate_kernel_tables(
     symbol_path: impl AsRef<Path>,
     kernel: KernelBuilt,
@@ -105,7 +126,7 @@ pub fn build_all(env: &BuildEnv) -> Result<AllBuilt, Error> {
 
     // Build target-specific image
     let image = match env.arch {
-        Arch::riscv64 => AllBuilt::Riscv64(kernel, initrd),
+        Arch::riscv64 => AllBuilt::Riscv64(make_kernel_bin(env, kernel, check)?, initrd),
         Arch::aarch64 => AllBuilt::AArch64(kernel, initrd),
         Arch::x86_64 => AllBuilt::X86_64(x86_64::build_image(env, kernel, initrd)?),
         Arch::i686 => AllBuilt::I686(i686::build_image(env, kernel, initrd)?),
diff --git a/xtask/src/env.rs b/xtask/src/env.rs
index 49831a92..74b7e510 100644
--- a/xtask/src/env.rs
+++ b/xtask/src/env.rs
@@ -81,9 +81,14 @@ pub enum Board {
     #[default]
     default,
 
+    // Generic aarch64/riscv board
+    virt,
+
     // AArch64 boards
     raspi4b,
-    virt,
+
+    // RISC-V boards
+    jh7110,
 }
 
 #[derive(Debug)]
@@ -136,6 +141,7 @@ impl BuildEnv {
         let kernel_triple = match (arch, board) {
             (Arch::aarch64, Board::virt | Board::default) => "aarch64-unknown-qemu",
             (Arch::riscv64, Board::virt | Board::default) => "riscv64-unknown-qemu",
+            (Arch::riscv64, Board::jh7110) => "riscv64-unknown-jh7110",
             (Arch::aarch64, Board::raspi4b) => "aarch64-unknown-raspi4b",
             (Arch::x86_64, Board::default) => "x86_64-unknown-none",
             (Arch::i686, Board::default) => "i686-unknown-none",
@@ -223,7 +229,7 @@ impl Profile {
 
 impl Arch {
     pub fn all() -> impl Iterator<Item = Self> {
-        [Self::aarch64, Self::x86_64, Self::i686].into_iter()
+        [Self::aarch64, Self::x86_64, Self::i686, Self::riscv64].into_iter()
     }
 
     pub fn user_triple(&self) -> &str {
diff --git a/xtask/src/error.rs b/xtask/src/error.rs
index f2685386..307847a6 100644
--- a/xtask/src/error.rs
+++ b/xtask/src/error.rs
@@ -4,6 +4,8 @@ use std::{
     process::{Command, ExitStatusError},
 };
 
+use crate::env::Board;
+
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
     #[error("{0}")]
@@ -47,6 +49,9 @@ pub enum Error {
     CompilerRtConfigFailed(CommandFailed),
     #[error("compiler-rt build failed: {0}")]
     CompilerRtBuildFailed(CommandFailed),
+
+    #[error("No qemu support for board {0:?}")]
+    UnsupportedEmulation(Board),
 }
 
 #[derive(Debug, thiserror::Error)]
diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs
index ce4fadcd..8e6fa1cb 100644
--- a/xtask/src/qemu.rs
+++ b/xtask/src/qemu.rs
@@ -11,7 +11,7 @@ use qemu::{
 };
 
 use crate::{
-    build::{self, AllBuilt, ImageBuilt, InitrdGenerated, KernelBuilt, KernelProcessed},
+    build::{self, AllBuilt, ImageBuilt, InitrdGenerated, KernelBin, KernelBuilt, KernelProcessed},
     env::{Board, BuildEnv},
     error::Error,
     util::run_external_command,
@@ -177,6 +177,9 @@ fn run_aarch64(
             .with_serial(QemuSerialTarget::MonStdio)
             .with_cpu(aarch64::Cpu::Max)
             .with_memory_megabytes(config.machine.aarch64.memory),
+        _ => {
+            return Err(Error::UnsupportedEmulation(env.board));
+        }
     };
 
     if env.board != Board::raspi4b {
@@ -354,7 +357,7 @@ pub fn run(
     add_devices_from_config(&mut devices, disk.as_ref(), &config)?;
 
     let mut command = match built {
-        AllBuilt::Riscv64(KernelProcessed(KernelBuilt(kernel)), InitrdGenerated(initrd)) => {
+        AllBuilt::Riscv64(KernelBin(kernel), InitrdGenerated(initrd)) => {
             run_riscv64(&config, &env, qemu, devices, kernel, initrd)?
         }
         AllBuilt::AArch64(KernelProcessed(KernelBuilt(kernel)), InitrdGenerated(initrd)) => {