linux源码解析05–ioremap原理

1,103次阅读
没有评论

补充点背景知识:

(1)根据不同架构处理器,对内存的访问分为两种方式;
a.x86架构,将外设和普通内存分开,通过专门的I/O指令(IN/OUT)来访问外设的寄存器,称为“I/O地址空间”或“I/O端口空间”;

b.RISC的CPU,比如ARM/PowerPC等,采用统一编制,即将所有I/O外设的内存空间看作普通内存的一部分;

(2)一般外设I/O的物理地址是已知的,但是开启MMU后,只能通过虚拟地址访问,因此需要将外设地址映射到虚拟地址;

ioremap映射函数

常用映射函数有

#define ioremap(addr, size)        __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRE))
#define ioremap_wc(addr, size)     __ioremap((addr), (size), __pgprot(PROT_NORMAL_NC))
#define ioremap_np(addr, size)     __ioremap((addr), (size), __pgprot(PROT_DEVICE_nGnRnE))

核心实现如下

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.
     */
    if (WARN_ON(pfn_valid(__phys_to_pfn(phys_addr))))
        return NULL;

    area = get_vm_area_caller(size, VM_IOREMAP, caller);  ///获得一个vma区
    if (!area)
        return NULL;
    addr = (unsigned long)area->addr;
    area->phys_addr = phys_addr;
///物理地址映射到vmalloc区
    err = ioremap_page_range(addr, addr + size, phys_addr, prot);
    if (err) {
        vunmap((void *)addr);
        return NULL;
    }

    return (void __iomem *)(offset + addr);   ///分配的地址是页对齐,加上偏移得到真实地址
}

ioremap_page_range函数

这部分跟之前普通页面映射差不多,就略过了;

建立好映射之后,应用程序可以通过虚拟地址,访问寄存器地址;

正文完
 0
admin
版权声明:本站原创文章,由 admin 于2022-03-23发表,共计1410字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
粤ICP备2021172357号-1