当我们写一些外设驱动,或者内核模块,经常会使用ioremap函数来映射寄存器io地址,来访问io空间,在ioremap函数的实现内部会对传入的参数进行判断,如果地址不属于io空间,则会内核会报Warnig。
从函数名字我们也可以看得出来,既然函数叫io remap,那么映射的地址空间必须是io的物理地址,所以如果映射的地址落在了ram地址空间,内核就会报warning!如下面的log
内核报warning的日志:
[ 2.536156] ------------[ cut here ]------------
[ 2.540766] WARNING: CPU: 0 PID: 1 at __ioremap_caller+0xd0/0xd8
[ 2.546759] Modules linked in:
[ 2.549804]
[ 2.551286] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 4.9.30-rt19-EMBSYS-CGEL-6.1.R3-g51292b3-dirty #26
[ 2.560664] Hardware name: MCS2 (DT)
[ 2.564227] task: ffffffc2d7c98d00 task.stack: ffffffc2d7c9c000
[ 2.570134] PC is at __ioremap_caller+0xd0/0xd8
[ 2.574652] LR is at __ioremap_caller+0x54/0xd8
[ 2.579170] pc : [<ffffff80080964e8>] lr : [<ffffff800809646c>] pstate: 80000045
[ 2.586551] sp : ffffffc2d7c9fd40
[ 2.589853] x29: ffffffc2d7c9fd40 x28: 0000000000000000
[ 2.595156] x27: ffffff80085bad50 x26: ffffff80085b5da8
[ 2.600459] x25: ffffff8008588aa0 x24: ffffff800864e950
[ 2.605762] x23: 00e8000000000707 x22: ffffff80085a3ab8
[ 2.611066] x21: 0000000000000000 x20: 0000000100200000
[ 2.616369] x19: 0000000001400000 x18: 0000000000000010
[ 2.621672] x17: 0000000000000001 x16: 0000000000000019
[ 2.626975] x15: 0000000000000006 x14: ffffff8088623bb7
[ 2.632278] x13: ffffff8008623bc5 x12: 0000000000000030
[ 2.637581] x11: 0101010101010101 x10: 7f7f7f7f7f7f7f7f
[ 2.642884] x9 : 6c646c616b69feff x8 : 0000000020000000
[ 2.648187] x7 : 0000000000000018 x6 : ffffff800863fb28
[ 2.653490] x5 : 0000000000000002 x4 : 0000000000000002
[ 2.658793] x3 : 0000000120000000 x2 : ffffff800863fb40
[ 2.664096] x1 : 0000000000000001 x0 : 0000000000000001
[ 2.669398]
[ 2.670880] ---[ end trace 52179c7400ddff0c ]---
[ 2.675485] Call trace:
[ 2.677921] Exception stack(0xffffffc2d7c9fb70 to 0xffffffc2d7c9fca0)
[ 2.684349] fb60: 0000000001400000 0000008000000000
[ 2.692166] fb80: ffffffc2d7c9fd40 ffffff80080964e8 ffffffc2d7c05100 ffffffc2d174c240
[ 2.699983] fba0: ffffff80082e724c 00000000000000c0 00000000000003c0 000000007fffffff
[ 2.707800] fbc0: 000000000000007f ffffff80086497c8 ffffffc2d7c1c608 0000000000000001
[ 2.715617] fbe0: 00000000000003c0 000000007fffffff ffffff8008588aa0 ffffff80086497c8
[ 2.723433] fc00: ffffffc2d7c9fc30 ffffff80082e724c 0000000000000001 0000000000000001
[ 2.731250] fc20: ffffff800863fb40 0000000120000000 0000000000000002 0000000000000002
[ 2.739067] fc40: ffffff800863fb28 0000000000000018 0000000020000000 6c646c616b69feff
[ 2.746884] fc60: 7f7f7f7f7f7f7f7f 0101010101010101 0000000000000030 ffffff8008623bc5
[ 2.754701] fc80: ffffff8088623bb7 0000000000000006 0000000000000019 0000000000000001
[ 2.762518] [<ffffff80080964e8>] __ioremap_caller+0xd0/0xd8
[ 2.768077] [<ffffff8008096500>] __ioremap+0x10/0x18
[ 2.773032] [<ffffff80085a3ab8>] memblk_init+0x98/0x240
[ 2.778246] [<ffffff8008082938>] do_one_initcall+0x38/0x120
[ 2.783808] [<ffffff8008590cd0>] kernel_init_freeable+0x18c/0x228
[ 2.789892] [<ffffff800847d658>] kernel_init+0x10/0x100
[ 2.795105] [<ffffff8008082700>] ret_from_fork+0x10/0x50
[ 2.801629] thread-exit capture initialized!
追溯一下代码,让我们找到真正的原因,先找到ioremap的定义,ioremap是一条宏定义:(arch/arm64/include/asm/io.h)
#define ioremap(addr, size) __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
__ioremap函数的定义如下,最终会调用__ioremap_caller函数:(arch/arm64/mm/ioremap.c)
void __iomem *__ioremap(phys_addr_t phys_addr, size_t size, pgprot_t prot)
{
return __ioremap_caller(phys_addr, size, prot,
__builtin_return_address(0));
}
__ioremap_caller函数实现很短,注意其中的WARN_ON()函数,WARN_ON是内核常用的断言,当条件为真时,会调用dump_stack打印堆栈,就会看到上面的一长串warning打印,相应的断言还有BUG_ON。如果触发了BUG_ON,内核一般会panic。但WARN_ON不会使内核崩溃。
下面函数中调用了WARN_ON来进行物理地址检查,并且,由注释“Don't allow RAM to be mapped.”也可以看出,ioremap不允许映射ram地址。
static void __iomem *__ioremap_caller(phys_addr_t phys_addr, size_t size,
pgprot_t prot, void *caller)
{
unsigned long last_addr;
unsigned long offset = phys_addr & ~PAGE_MASK;
int err;
unsigned long addr;
struct vm_struct *area;
/*
* Page align the mapping address and size, taking account of any
* offset.
*/
phys_addr &= PAGE_MASK;
size = PAGE_ALIGN(size + offset);
/*
* Don't allow wraparound, zero size or outside PHYS_MASK.
*/
last_addr = phys_addr + size - 1;
if (!size || last_addr < phys_addr || (last_addr & ~PHYS_MASK))
return NULL;
/*
* Don't allow RAM to be mapped.
*/
WARN_ON(pfn_valid(__phys_to_pfn(phys_addr))); /* 检查phys_addr是否是一个有效的物理地址 */
area = get_vm_area_caller(size, VM_IOREMAP, caller);
if (!area)
return NULL;
addr = (unsigned long)area->addr;
area->phys_addr = phys_addr;
err = ioremap_page_range(addr, addr + size, phys_addr, prot);
if (err) {
vunmap((void *)addr);
return NULL;
}
return (void __iomem *)(offset + addr);
}
kernel用pfn_valid(__phys_to_pfn(phys_addr))来判断该地址是否为一个有效的系统ram地址,因为物理地址可以转换成一个有效的页帧号。
---以上代码摘自linux-4.9.30版本内核。