Hi Diego,
Thanks for your patch series!
I applied the first patch with minor changes. The second patch is also
ambitious and defines a lot of useful stuff we need (CSR definitions,
writes/reads, etc.). However, I can not apply it as is, because there
are correctness and portability concerns. The pmap impl is one of the
most important foundations of the port, so I am being extra careful
in this area.
Here are some of my concerns, beginning from patch 2:
> + pmap_discover_physical_memory(&node);
>
> +void
> +pmap_discover_physical_memory(struct dtb_node *node)
This repeats the physical memory discovery implementation already
introduced in patch 1. Perhaps `git format-patch` was invoked
improperly?
> + bootstrap_heap = round_page((vm_offset_t) _end);
This assumes all memory after the kernel image is available. It does
not account for the DTB potentially residing immediately after the
kernel, or near the end of RAM.
> + kernel_virtual_start = phystokv(round_page(phys_mem_start +
> phys_mem_size));
> + kernel_virtual_end = VM_MAX_KERNEL_ADDRESS - PAGE_SIZE;
>
> + directmap_end = _kvtophys(kernel_virtual_start);
phystokv(x) was defined as x + 0xffffffff80000000, so, assuming a
QEMU physical address range, this would wrap back to 0. The translation
mechanism of phystokv() should have been changed before this. (Updated,
see [1])
> + nr_phys_pages = (directmap_end - 0x80000000UL) / PAGE_SIZE;
The physical base is hard-coded to QEMU virt RAM. Other systems (including
my development board Milk-V Mars) place RAM elsewhere. The DTB has already
supplied phys_mem_start, there is no need to hard-code this.
> +#define MAX_L0_TABLES 16
>
> + num_l0_tables = (nr_phys_pages * 512 + (2 * 1024 * 1024) - 1)
> + / (2 * 1024 * 1024) + 4;
> + if (num_l0_tables < 8)
> + num_l0_tables = 8;
> + if (num_l0_tables > MAX_L0_TABLES)
> + num_l0_tables = MAX_L0_TABLES;
Why is this capped to 16? A small comment would be helpful.
> + unsigned int num_kernel = (directmap_end - 0x80000000)
> + / (2*1024*1024);
>
> + for (idx = 0; idx < num_kernel; idx++) {
> + pa = 0x80000000UL + (vm_offset_t)idx * (2 * 1024 *
> 1024);
> + l1_kernel[idx] = pa_to_pte(pa) | pte_flags;
> + }
This again assumes that RAM begins at 0x80000000.
> + unsigned int num_devices = 136; /* 136 × 2MB = 272MB, covers
> up to 0x11000000 */
> + for (idx = 0; idx < num_devices; idx++) {
> + pa = (vm_offset_t)idx * (2 * 1024 * 1024);
> + l1_devices[idx] = pa_to_pte(pa) | pte_flags;
> + }
This hardcodes QEMU's device layout. Device mappings must come from DTB,
we should not assume a specific board.
The comment also contains a non-ASCII multiplication sign.
> + l1_kernel[64 + idx] = pa_to_pte(l0_pas[idx]) |
> ptr_flags;
Index 64 assumes that the kernel allocation area starts exactly 128 MiB
into the L1 table. It is not derived from kernel_virtual_start.
> + root_table[0] = pa_to_pte(l1d_pa) | ptr_flags;
> + root_table[2] = pa_to_pte(l1k_pa) | ptr_flags;
> + root_table[510] = pa_to_pte(l1k_pa) | ptr_flags;
These root indices encode a fixed QEMU physical layout.
> + satp_write(satp_val);
> + sfence_vma();
> + }
>
> + /*
> + * Now paging is ON. The identity map at root[2] covers
> + * VA 0x80000000 → PA 0x80000000, so the code at ~0x802xxxxx
> + * keeps working.
We enabled paging, but we're still on the lower address, we are not
jumping to the higher half kernel memory in this patch. In the case we
did, the ldscript lays the VMAs of symbols starting from _START_MAP, so
pointers within globals will be directed to the lower half memory,
which means the lower half can not be safely unmapped. The ldscript
should have been tweaked before this. (Updated, see [1])
The comment also contains a non-ASCII arrow.
> + if (RISCV_PTE_IS_LEAF(*pte)) {
> + /*
> + * Already mapped by a megapage. For bootstrap, this is fine —
> + * the direct map already covers this PA. Nothing to do.
> + */
> + PMAP_READ_UNLOCK(pmap, spl);
> + return;
> + }
A leaf mapping cannot be accepted merely because it covers the requested
virtual address. Its physical address and permissions may differ from
the requested mapping. Silently returning here is not desirable.
This path also doesn't split a megapage when a page-level mapping or
protection change is required.
> + template = pa_to_pte(pa) | RISCV_PTE_V | RISCV_PTE_A | RISCV_PTE_D;
> + if (pmap != kernel_pmap)
> + template |= RISCV_PTE_U;
> + if (prot & VM_PROT_WRITE)
> + template |= RISCV_PTE_W;
riscv reserves the W=1, R=0 PTE encoding. A request containing write perms
without read permission would produce an invalid PTE.
> + WRITE_PTE(pte, template);
> + sfence_vma();
>
> + PMAP_UPDATE_TLBS(pmap, v, v + PAGE_SIZE);
This overwrites existing mappings at the hardware-level without maintaining
the software resident and wired counts, or removing the old pmap mappings.
> +#define RISCV_PT_SHIFT 12 /* page table covers 2^12 =
> 4096 entries? No: 2^9 = 512 */
What did you mean by the comment here? Twelve is the page-offset width,
each sv39 table is indexed by nine bits and contains 512 entries.
> +#define RISCV_PTE_PPN_MASK 0x3FFFFFFFFFFFFC00UL
This mask includes reserved upper PTE bits. Specifically, it occupies
61:10. Sv39 PPN occupies bits 53 through 10, so the mask must not
extend that much.
> +/* ---- Compat aliases mapping Intel PTE names to RISC-V ---- */
>
> +#define INTEL_PTE_VALID RISCV_PTE_V
> +#define INTEL_PTE_WRITE RISCV_PTE_W
> +#define INTEL_PTE_PS 0
> +#define INTEL_PTE_WIRED 0
This is actually unnecessary, we shouldn't keep the baggage carried over
from the initial stub port. x86-specific definitions should be removed,
and only riscv ones should remain. (Updated, see [1])
**
Those aside, scaffolding part of the second patch (CSR definitions,
write/read helper macros, etc.) was clean and I applied it.
**
[1]: Considering these, and looking at the impartial foundation of
the master branch before beginning sv39 mapping work (i.e. ldscript
not using higher half memory, phystokv/kvtophys contract not being
clear, Intel definitions leaking through), I created a new branch
named `high-address-kernel` where paging work can begin cleanly.
In this branch (high-address-kernel), I:
* Changed the VMA of the kernel to start from 0xffffffff80000000 instead
of the previous VMA of 0x80000000. The kernel proper should live in
higher half memory.
* Changed the LMA of the kernel to begin from 0 (Similar to Bugaev's),
and use objcopy to flatten the kernel image. QEMU, or any other SBC
will load the kernel at an available address instead of trying and
failing to obtain ELF-LMA physical address area.
* Defined the direct-mapped range to begin from 0xffffffc000000000
and changed phystokv/kvtophys appropriately.
* Deferred the console initialization, because the updated ns16550 driver
reads from statically initialized memory which embeds a (high) VMA address.
The console will be enabled after paging. (Earlycon to be impl'd later)
Also I axed the x86 definitions that were carried over from the initial
stub port (My bad! Those should have never been there)
I tested the new arrangement via QEMU, and verified that the early
boot phase environment does not dereference high address VMA. The
path to pmap_bootstrap() is clean and ready.
This prepares the codebase properly so that paging work can begin for
a high-address kernel.
After paging is implemented and we get printf working from higher half
memory, the changes will be merged back to master. I am opting to not
leave the master branch in an intermediate state and keep the existing
banner demo working.
If you want to iterate on this based on the high-address-kernel branch,
let me know. Alternatively, since the early bootstrap interacts so heavily
with the updated link layout and direct-mapping convention, I can take
the lead on implementing the initial Sv39 mapping and higher-half trampoline.
Either way, any code adapted from your patch will keep your attribution.
A small side note: For the next patches, we should continue on separate
threads, as usual in mailing list based patch review. The diffs should
be provided inline in the mail and not be provided as an attachment,
as the former approach makes the review easier for everyone, including
the reviewer and the observers :)
To send patches easily from your terminal, you can use git send-email,
which does those automatically for you. i.e. via:
git format-patch --cover-letter --subject-prefix="PATCH gnumach riscv64" -o
outgoing/ [RANGE]
git send-email --to="[email protected]" outgoing/*.patch
Let's use the prefix "gnumach riscv64" so that riscv-specific patches
are immediately distinguishable from patches meant for upstream. I think
this will prevent confusion among maintainers and observers of the list.
This one was a long read, so I appreciate you taking your time to read
it. The patches are valuable, and I am glad we are getting the riscv
port going.
Have a nice week!
Hakan