I'm writing a kernel module that needs access to a memory region outside the Kernel and User spaces. I know the physical address and the size of such a memory. I've tried with ioremap(), but the virtual address returned by such a function points to a phisical address different from the one I gave as an argument to ioremap. Then I tried to use phys_to_virt() after ioremap(), this time the virtual address obtained points to the correct physical one, however when I try to access an element inside such memory area I get the following error: Unable to handle kernel paging request at virtual address ffffff801400000f What I'm wrong?
EDIT: Code snippet:
#define FLAG_SIZE 1
#define BUF_SIZE 4
#define ELEM_SIZE 5
res = ioremap_nocache(MEMORY_ADDR, BUF_SIZE*ELEM_SIZE);
void *virt = phys_to_virt(MEMORY_ADDR);
printk(KERN_INFO "Physical address %px mapped in virtual address %px\n", virt_to_phys(virt), virt);
// DEBUG: print buffers content
char *flags = res + BUF_SIZE*(ELEM_SIZE-FLAG_SIZE);
for(i=0; i < BUF_SIZE; i++){
printk(KERN_INFO "row %d [flag: %c]: %d\n", i, readb(flags + i), virt + i*ELEM_SIZE);
}
where: -MEMORY_ADDR: is the physical address -BUF_SIZE: number of elements in the buffer -ELEM_SIZE: size of the elements
The flags buffers is an array of chars at the end of such memory region
The full outputs returned are:
Physical address 1409286144 mapped in virtual address 335544320
Unable to handle kernel paging request at virtual address ffffff801400000f
Mem abort info:
ESR = 0x96000006
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
Data abort info:
ISV = 0, ISS = 0x00000006
CM = 0, WnR = 0
swapper pgtable: 4k pages, 39-bit VAs, pgdp=00000000407c8000
[ffffff801400000f] pgd=000000004fffb003, pud=000000004fffb003, pmd=0000000000000000
Internal error: Oops: 96000006 [#1] SMP
Modules linked in: source(O)
CPU: 1 PID: 107 Comm: ifconfig Tainted: G O 5.4.88 #1
Hardware name: linux,dummy-virt (DT)
pstate: 20000005 (nzCv daif -PAN -UAO)
pc : getFlag+0x24/0x40 [source]
lr : shmem_net_open+0x168/0x2c0 [source]
sp : ffffffc010b4b9d0
x29: ffffffc010b4b9d0 x28: 0000000000001043
x27: ffffff800e2b4c00 x26: ffffffc01077c000
x25: 0000000000000005 x24: ffffffc008661158
x23: 0000000000000004 x22: 0000000000000000
x21: ffffff800e24e200 x20: ffffff800e255000
x19: ffffff800e255858 x18: 0000000000020000
x17: 000000004a0b804e x16: 0000000031b76cb0
x15: ffffffc010859c40 x14: 0720072007200720
x13: 0720073007320733 x12: 0734073407350735
x11: 0733073307200773 x10: 0773076507720764
x9 : 076407610720076c x8 : 000000000000006f
x7 : 076907760720076e x6 : 0000000000000001
x5 : ffffff800e255840 x4 : 0000000000000005
x3 : 0000008040000000 x2 : 0000000000000000
x1 : 000000000000000f x0 : ffffff801400000f
Call trace:
getFlag+0x24/0x40 [source]
__dev_open+0xe4/0x160
__dev_change_flags+0x160/0x1c0
dev_change_flags+0x20/0x60
devinet_ioctl+0x63c/0x700
inet_ioctl+0x2f4/0x360
sock_do_ioctl+0x44/0x2b0
sock_ioctl+0x1c8/0x510
do_vfs_ioctl+0x984/0xb70
ksys_ioctl+0x44/0x90
__arm64_sys_ioctl+0x1c/0xc0
el0_svc_common.constprop.0+0x68/0x160
el0_svc_handler+0x6c/0x90
el0_svc+0x8/0x1fc
Code: 1b047c21 8b21c041 8b010000 cb030000 (39400000)
---[ end trace 5a219c0b95978c47 ]---
Segmentation fault
The processor used is an Armv8 cortex 53
EDIT 2: corrected the previous code snippets, now no kernel panic, however the output is incorrect:
[SHMEM_NET] Physical address 0000000054000000 mapped in virtual address ffffffc01000d000
row 0 [flag: ]: 268488704
row 1 [flag: E]: 268488707
row 2 [flag: E]: 268488710
row 3 [flag: E]: 268488713
row 4 [flag: E]: 268488716
The issue is that I should find the first 'E' at row 0 and row 4 should be instead outside the mapped region. Moreover if now I modify such memory region, the modifications can be seen by this machine, but other machines accessing same memory regions still sees the old values.
The virtual address' returned by ioremap
and phys_to_virt
are not the same.
ioremap
: creates a new mapping page table
phys_to_virt
: substract a fixed offset from the address. Check MMU for phy_offset.
How to test?
void* addr1 = phys_to_virt(MEMORY_ADDR);
void* addr2 = ioremap_nocache(MEMORY_ADDR, BUF_SIZE*ELEM_SIZE);
printk(KERN_INFO "virt_addr1: %x\n", (unsigned int volatile *) addr1);
printk(KERN_INFO "virt_addr2: %x\n", (unsigned int volatile *) addr2);
You will see addr1 and addr2 are different. I think you dont need to use phys_to_virt
.
Simply use:
void* virt_addr = ioremap_nocache(MEMORY_ADDR, BUF_SIZE*ELEM_SIZE);
User contributions licensed under CC BY-SA 3.0