ARM Cortex-A11 PL011 UART Virtual Address Mapping Challenges
When porting the Linux kernel to a custom ARM1176JZFS-based SoC, one of the critical tasks is configuring the PL011 UART for low-level debugging. The PL011 UART, a PrimeCell peripheral, is physically mapped to a specific address in the SoC’s memory space. In this case, the UART is mapped to the physical address 0x58001000
. However, the kernel requires a virtual address to access this peripheral during the early boot stages when the Memory Management Unit (MMU) is enabled. This requirement often leads to confusion, especially for developers new to kernel porting.
The primary issue arises when the kernel fails to boot or hangs after displaying the message "Starting kernel… Uncompressing Linux… done, booting the kernel." This behavior is often tied to incorrect virtual address mapping for the PL011 UART or other peripherals, such as the Vectored Interrupt Controller (VIC). The kernel’s inability to handle paging requests at specific virtual addresses further complicates the situation, as seen in the error logs:
[ 0.000000] Unable to handle kernel paging request at virtual address 48000fe0
[ 0.000000] pgd = c0004000
[ 0.000000] [48000fe0] *pgd=00000000
[ 0.000000] Internal error: Oops: 5 [#1] ARM
This error indicates that the kernel is attempting to access a virtual address (0x48000fe0
) that has not been properly mapped, leading to a translation fault. The root cause of this issue lies in the improper handling of physical-to-virtual address translation for peripherals like the PL011 UART and VIC.
Physical-to-Virtual Address Translation and MMU Configuration
The ARM architecture relies on the MMU to manage virtual memory, translating virtual addresses used by the kernel into physical addresses in hardware. When the kernel is booting, it enables the MMU early in the process, which means that all memory accesses must use virtual addresses. This includes accesses to peripherals like the PL011 UART and VIC, which are mapped to specific physical addresses in the SoC.
In the case of the PL011 UART, the physical address 0x58001000
must be mapped to a virtual address that the kernel can use. The kernel provides mechanisms like ioremap()
to handle this translation dynamically. However, during early boot, before the full kernel memory management subsystem is initialized, the kernel relies on fixed virtual address mappings for critical peripherals like the debug UART.
The PHYS_OFFSET
and PAGE_OFFSET
configuration options play a crucial role in defining the kernel’s memory layout. PHYS_OFFSET
specifies the physical address where the kernel expects to find the start of RAM, while PAGE_OFFSET
defines the start of the kernel’s virtual address space. In this case, both are set to 0xC0000000
, which aligns with the custom evaluation board’s DDR memory mapping (0xC0000000
to 0xCFFFFFFF
).
However, the VIC peripheral, physically mapped at 0x48000000
, falls outside this range. Attempting to access this peripheral without proper virtual address mapping results in a translation fault, as the kernel cannot resolve the virtual address 0x48000fe0
to a valid physical address.
Resolving Virtual Address Mapping and Kernel Boot Issues
To resolve these issues, developers must ensure that all peripherals are properly mapped to virtual addresses before they are accessed. For the PL011 UART, the kernel’s low-level debugging functions require a virtual address to be specified. This can be achieved by modifying the kernel’s early debug UART configuration to include the correct virtual address mapping.
For the VIC peripheral, the ioremap()
function must be used to obtain a virtual address before accessing the hardware. The following code snippet demonstrates how to map the VIC peripheral and initialize it:
void __iomem *vic_base;
int i;
for (i = 0; i < 2; i++) {
if (!request_mem_region(bbsoc_vic[i].start, VIC_SIZE, "Vic")) {
pr_err("Vic_%d Request mem region failed.\n", i);
return -1;
}
vic_base = ioremap(bbsoc_vic[i].start, VIC_SIZE);
if (!vic_base) {
pr_err("VIC_%d : ioremap failed...\n\r", i);
release_mem_region(bbsoc_vic[i].start, VIC_SIZE);
return -1;
}
if (i == 0)
vic_init(vic_base, 0, BBSOC_VIC0_IRQ_MASK, 0);
if (i == 1)
vic_init(vic_base, 32, BBSOC_VIC0_IRQ_MASK, 0);
}
This code ensures that the VIC peripheral is properly mapped to a virtual address before it is accessed, preventing translation faults during kernel boot.
For the PL011 UART, developers should verify that the virtual address specified in the kernel configuration matches the physical address mapping. If the kernel continues to hang after "booting the kernel," additional debugging steps may be required, such as enabling CONFIG_DEBUG_INFO
in the kernel configuration and using a debugger to inspect the kernel’s memory mappings and boot process.
By carefully managing physical-to-virtual address translation and ensuring that all peripherals are properly mapped, developers can resolve kernel boot issues and successfully port the Linux kernel to custom ARM-based SoCs.