注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

火车的家

Put first thing first

 
 
 

日志

 
 

2014.02.06 ioremap 的实现分析  

2015-03-16 14:02:49|  分类: linux kernel |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

本文基于“友善之臂”的 mini2440 平台和内核版本 2.6.32.2 分析 linux ioremap 的实现。文章对 linux 内存子系统的重要数据结构及函数做了一个简单介绍。

1. ioremap
对于mini2440平台,ioremap是一个宏
#define ioremap(cookie,size)            __arm_ioremap(cookie, size, MT_DEVICE)
对于某些特定的 arm 平台,ioremap会被定义为__arch_ioremap,也就是使用这些平台的特定实现。

__arm_ioremap函数先把物理地址转换为Page Frame Number,并且计算页内偏移 offset。对于mini2440,内存页是4k,有下面几个重要的宏。

#define PAGE_SHIFT              12
#define PAGE_SIZE               (_AC(1,UL) << PAGE_SHIFT)
#define PAGE_MASK               (~(PAGE_SIZE-1))
#define __phys_to_pfn(paddr)    ((paddr) >> PAGE_SHIFT)

void __iomem *
__arm_ioremap(unsigned long phys_addr, size_t size, unsigned int mtype)
{
        unsigned long last_addr;
        unsigned long offset = phys_addr & ~PAGE_MASK;
        unsigned long pfn = __phys_to_pfn(phys_addr);

        /*
         * Don't allow wraparound or zero size
         */
        last_addr = phys_addr + size - 1;
        if (!size || last_addr < phys_addr)
                return NULL;

        return __arm_ioremap_pfn(pfn, offset, size, mtype);
}
EXPORT_SYMBOL(__arm_ioremap);

2. __arm_ioremap_pfn
源代码就是文档,请直接参考源代码里面的注释。

void __iomem *
__arm_ioremap_pfn(unsigned long pfn, unsigned long offset, size_t size,
                   unsigned int mtype)
{
        const struct mem_type *type;
        int err;
        unsigned long addr;
        struct vm_struct * area;

        /*
         * High mappings must be supersection aligned
         */
        // suppersection 是什么?
        // 参考这两份文档,http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344k/Cihbdgab.html
        // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344k/Cihbdgab.html
        // 如果arm 支持超过 4G 的内存空间,那么超过 4G 的部分就是 suppersection ,suppersection 是
        // 由 16M 的内存块构成的。suppersection 的内存 pfn 一定要以 16M 对齐。
        if (pfn >= 0x100000 && (__pfn_to_phys(pfn) & ~SUPERSECTION_MASK))
                return NULL;

        // 获取指定内存的属性,这个函数还可以深挖。我看 MT_DEVICE 也是在 mem_types 里面定义的,
        // 以后有空再研究。
        type = get_mem_type(mtype);
        if (!type)
                return NULL;

        /*
         * Page align the mapping size, taking account of any offset.
         */
        size = PAGE_ALIGN(offset + size);

        //查找有没有合适大小的内存虚地址空间,get_vm_area 也值得深挖。下次再说。
        area = get_vm_area(size, VM_IOREMAP);
        if (!area)
                return NULL;
        addr = (unsigned long)area->addr;

#ifndef CONFIG_SMP
        if (DOMAIN_IO == 0 &&
            (((cpu_architecture() >= CPU_ARCH_ARMv6) && (get_cr() & CR_XP)    ) ||
               cpu_is_xsc3()) && pfn >= 0x100000 &&
               !((__pfn_to_phys(pfn) | size | addr) & ~SUPERSECTION_MASK))     {
                area->flags |= VM_ARM_SECTION_MAPPING;
                err = remap_area_supersections(addr, pfn, size, type);
        } else if (!((__pfn_to_phys(pfn) | size | addr) & ~PMD_MASK)) {
                area->flags |= VM_ARM_SECTION_MAPPING;
                err = remap_area_sections(addr, pfn, size, type);
        } else {
#endif
                //以上两个分支在做什么没看明白,但是一般驱动里面做动态映射,都会走到下面这个分支
                err = remap_area_pages(addr, pfn, size, type);
#ifndef CONFIG_SMP
        }
#endif

        if (err) {
                vunmap((void *)addr);
                return NULL;
        }

        // 完成映射后先 flush 一下cache,以免 cache 的内容被破坏。
        flush_cache_vmap(addr, addr + size);
        return (void __iomem *) (offset + addr);
}
EXPORT_SYMBOL(__arm_ioremap_pfn);

3. remap_area_pages
arm 的 pgd 有 2048 项,每项为 2M 空间。

#define PGDIR_SHIFT             21
/* to find an entry in a page-table-directory */
#define pgd_index(addr)         ((addr) >> PGDIR_SHIFT)
#define pgd_offset(mm, addr)    ((mm)->pgd+pgd_index(addr))
/* to find an entry in a kernel page-table-directory */
#define pgd_offset_k(addr)      pgd_offset(&init_mm, addr)

// start 是虚拟地址
static int remap_area_pages(unsigned long start, unsigned long pfn,
                            size_t size, const struct mem_type *type)
{
        unsigned long addr = start;
        unsigned long next, end = start + size;
        unsigned long phys_addr = __pfn_to_phys(pfn);
        pgd_t *pgd;
        int err = 0;

        BUG_ON(addr >= end);

        // 首先计算起始地址 addr 对应的 pgd
        // 对于内核空间来说,addr 的 pgd 就是它的高 11 位。
        pgd = pgd_offset_k(addr);
        do {
                // 一般来说,next 就是 addr 加一个 pgd 的 size,也就是 2M,
                // 但是也可能小于 2M。
                next = pgd_addr_end(addr, end);

                // 以 next - addr 为单位映射物理地址空间,一般为 2M。
                err = remap_area_pmd(pgd, addr, next, phys_addr, type);
                if (err)
                        break;
                phys_addr += next - addr;
        } while (pgd++, addr = next, addr != end);

        return err;
}

4. remap_area_pmd
源代码就是文档,请直接参考源代码里面的注释。
// addr 是虚拟地址, pgd 和 end 是 pgd 表项索引
static inline int remap_area_pmd(pgd_t *pgd, unsigned long addr,
                                 unsigned long end, unsigned long phys_addr,
                                 const struct mem_type *type)
{
        unsigned long next;
        pmd_t *pmd;
        int ret = 0;

        pmd = pmd_alloc(&init_mm, pgd, addr);
        if (!pmd)
                return -ENOMEM;

        do {
                next = pmd_addr_end(addr, end);
                ret = remap_area_pte(pmd, addr, next, phys_addr, type);
                if (ret)
                        return ret;
                phys_addr += next - addr;
        } while (pmd++, addr = next, addr != end);
        return ret;
}

对于 mini2440,pmd_alloc 的定义如下:
#define pmd_alloc(mm, pud, address) \
        ((unlikely(pgd_none(*(pud))) && __pmd_alloc(mm, pud, address))? \
                NULL: pmd_offset(pud, address))
因为 pgd_none 定义为 0,所以 __pmd_alloc 不会被调用到。

/* Find an entry in the second-level page table.. */
#define pmd_offset(dir, addr)   ((pmd_t *)(dir))
所以,pmd_offset 就是 pgd。

5. remap_area_pte
通过函数 set_pte_ext 设置页表
  评论这张
 
阅读(144)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017