Chinaunix首页 | 论坛 | 博客
  • 博客访问: 334237
  • 博文数量: 96
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 152
  • 用 户 组: 普通用户
  • 注册时间: 2013-09-02 09:27
文章分类

全部博文(96)

文章存档

2017年(2)

2016年(30)

2015年(38)

2014年(25)

2013年(1)

我的朋友

分类: LINUX

2015-12-02 16:18:31

原文地址:ioremap_nocache 函数分析(二) 作者:zd零

                            ioremap_nocache 函数分析(二)

非连续映射地址空间

static struct vm_struct *__get_vm_area_node(unsigned long size, unsigned long flags,

                                             unsigned long start, unsigned long end,

                                             int node, gfp_t gfp_mask)

 {

         struct vm_struct **p, *tmp, *area;

         unsigned long align = 1;

         unsigned long addr;

 

         BUG_ON(in_interrupt());

         if (flags & VM_IOREMAP) {

                 int bit = fls(size);

 

                 if (bit > IOREMAP_MAX_ORDER)

                         bit = IOREMAP_MAX_ORDER;

                 else if (bit < PAGE_SHIFT)

                         bit = PAGE_SHIFT;

 

                 align = 1ul << bit;

         }

         addr = ALIGN(start, align);

         size = PAGE_ALIGN(size);

         if (unlikely(!size))

                 return NULL;

 

         area = kmalloc_node(sizeof(*area), gfp_mask & GFP_LEVEL_MASK, node);

         分配虚拟页面结构

 

         if (unlikely(!area))

                 return NULL;

 

         /*

          * We always allocate a guard page.

          */

         size += PAGE_SIZE;

         作为隔离带

 

         write_lock(&vmlist_lock);

         查找之前的struct vmlist 链表(有序链表从小到大)

         查找到一个合理的虚拟地址空间

         for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next) {

                 if ((unsigned long)tmp->addr < addr) {

                         if((unsigned long)tmp->addr + tmp->size >= addr)

                                 addr = ALIGN(tmp->size +

                                              (unsigned long)tmp->addr, align);

                         continue;

                 }

                 if ((size + addr) < addr)

                         goto out;

                 回绕

                 if (size + addr <= (unsigned long)tmp->addr)

                         goto found;

                 addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);

 

                 if (addr > end - size)

                         goto out;

         }

 

 found:

         area->next = *p;

         *p 可能为NULL

         *p = area;

          新申请的 struct vm_struct 加入到 vmlist 链表中

 

         area->flags = flags;

         area->addr = (void *)addr;

         VMALLOC_START (线性地址)

         area->size = size;

         area->pages = NULL;

         area->nr_pages = 0;

         area->phys_addr = 0;

         write_unlock(&vmlist_lock);

 

         return area;

 

 out:

         write_unlock(&vmlist_lock);

         kfree(area);

         if (printk_ratelimit())

                 printk(KERN_WARNING "allocation failed: out of vmalloc space - use vmalloc= to increase size.\n");

         return NULL;

 }

 

 

注意:此时我们假设没有打开PAE!

 

 

(页目录)

int ioremap_page_range(unsigned long addr,

                       unsigned long end, unsigned long phys_addr, pgprot_t prot)

{

        pgd_t *pgd;

        unsigned long start;

        unsigned long next;

        int err;

 

        BUG_ON(addr >= end);

 

        start = addr;

        线性地址

 

        phys_addr -= addr;

        pgd = pgd_offset_k(addr);

        求出线性地址 addr 在页目录表中的地址

       

#define pgd_offset_k(address)     pgd_offset(&init_mm, address)

#define pgd_offset(mm, address)   ((mm)->pgd+pgd_index(address))

#define pgd_index(address)    (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1))

页目录表中的偏移

 

#define PGDIR_SHIFT     22

#define PTRS_PER_PGD    1024

 

        do {

                next = pgd_addr_end(addr, end);

                一般情况下返回 end(线性地址末端)

 

                err = ioremap_pud_range(pgd, addr, next, phys_addr+addr, prot);

                if (err)

                        break;

        } while (pgd++, addr = next, addr != end);

 

        flush_cache_vmap(start, end);

 

        return err;

        返回 0

}

 

 

#define pgd_addr_end(addr, end)                                         \

({      unsigned long __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK;  \

        (__boundary - 1 < (end) - 1)? __boundary: (end);                \

})

PGDIR_SIZE = 1<<22

PGDIR_MASK = 3FFFFF

addr end 之间大小不能超过4Mb,因为一个页目录项最多表示4Mb 内存

 

 

 

 

 

 

 

 

(页上层目录)

static inline int ioremap_pud_range(pgd_t *pgd, unsigned long addr,

                unsigned long end, unsigned long phys_addr, pgprot_t prot)

{

        参数:

        pgd 线性地址 addr 所表示的页目录地址

        addr 线性地址

        end 线性地址末端

        phys_addr =  EHCI 总线地址

        prot 标志

 

        pud_t *pud;

        unsigned long next;

 

        phys_addr -= addr;

        pud = pud_alloc(&init_mm, pgd, addr);

        返回的是pgd 页目录地址

 

        if (!pud)

                return -ENOMEM;

        do {

                next = pud_addr_end(addr, end);

                #define pud_addr_end(addr, end)                 (end)

 

                if (ioremap_pmd_range(pud, addr, next, phys_addr + addr, prot))

                        return -ENOMEM;

        } while (pud++, addr = next, addr != end);

        return 0;

}

 

 

 

 

 

static inline pud_t *pud_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long address)

{

        return (unlikely(pgd_none(*pgd)) && __pud_alloc(mm, pgd, address))?

                NULL: pud_offset(pgd, address);

}

 

static inline pud_t * pud_offset(pgd_t * pgd, unsigned long address)

{

        return (pud_t *)pgd;

}

(页中间目录)

static inline int ioremap_pmd_range(pud_t *pud, unsigned long addr,

                unsigned long end, unsigned long phys_addr, pgprot_t prot)

{

        pud pgd

 

        pmd_t *pmd;

        unsigned long next;

 

        phys_addr -= addr;

        pmd = pmd_alloc(&init_mm, pud, addr);

        pmd 还是为 pgd

 

        if (!pmd)

                return -ENOMEM;

        do {

                next = pmd_addr_end(addr, end);

                #define pmd_addr_end(addr, end)                 (end)

 

                if (ioremap_pte_range(pmd, addr, next, phys_addr + addr, prot))

                        return -ENOMEM;

        } while (pmd++, addr = next, addr != end);

        return 0;

}

 

 

static inline pmd_t *pmd_alloc(struct mm_struct *mm, pud_t *pud, unsigned long address)

{

        return (unlikely(pud_none(*pud)) && __pmd_alloc(mm, pud, address))?

                NULL: pmd_offset(pud, address);

}

static inline pmd_t * pmd_offset(pud_t * pud, unsigned long address)

{

        return (pmd_t *)pud;

}

 

 

 

 

 

 

 

 

 

(页表) 开始干正经事了

static int ioremap_pte_range(pmd_t *pmd, unsigned long addr,

                unsigned long end, unsigned long phys_addr, pgprot_t prot)

{

        pte_t *pte;

        unsigned long pfn;

 

        pfn = phys_addr >> PAGE_SHIFT;

        EHCI 控制器总线地址的页框号

 

        pte = pte_alloc_kernel(pmd, addr);

        创建页表(如果不存在)

        pte 为线性地址addr 在页表中的地址

 

        if (!pte)

                return -ENOMEM;

        do {

                BUG_ON(!pte_none(*pte));

                set_pte_at(&init_mm, addr, pte, pfn_pte(pfn, prot));

 #define pfn_pte(pfn, prot)      __pte(((pfn) << PAGE_SHIFT) | pgprot_val(prot))

 

                很明显最后一个参数为 EHCI 的总线地址加上标志位(设置到页表中的地址为总线地址)

               

                设置页表

 

                pfn++;

 

        } while (pte++, addr += PAGE_SIZE, addr != end);

        此时假设 EHCI映射到内存的 I/O MEM大小为 1024Kb,此处会循环设置

        return 0;

}

 

 

 

#define pte_alloc_kernel(pmd, address)                  \

        ((unlikely(!pmd_present(*(pmd))) && __pte_alloc_kernel(pmd, address))? \

                NULL: pte_offset_kernel(pmd, address))

 

 

 

 

 

int __pte_alloc_kernel(pmd_t *pmd, unsigned long address)

{

        pte_t *new = pte_alloc_one_kernel(&init_mm, address);

        申请一页页框作为页表

 

        if (!new)

                return -ENOMEM;

 

        spin_lock(&init_mm.page_table_lock);

        if (pmd_present(*pmd))          /* Another has populated it */

                pte_free_kernel(new);

        else

                pmd_populate_kernel(&init_mm, pmd, new);

                设置到页目录表中去(将页表地址填入到页目录中)

        spin_unlock(&init_mm.page_table_lock);

        return 0;

}

 

 

pte_t *pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address)

{

        return (pte_t *)__get_free_page(GFP_KERNEL|__GFP_REPEAT|__GFP_ZERO);

}

 

 

#define pte_offset_kernel(dir, address) \

       ((pte_t *) pmd_page_vaddr(*(dir)) +  pte_index(address))

 

线性地址addr 在页表中的地址

 

#define pmd_page_vaddr(pmd) \

                 ((unsigned long) __va(pmd_val(pmd) & PAGE_MASK))

 

#define pte_index(address) \

                (((address) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))

 

取线性地址的中间10

 

#define PTRS_PER_PTE    1024


调 用ioremap_nocache()函数之后,返回一个线性地址,此时CPU 可以访问设备的内存(已经将其映射到了线性地址空间中了),此时CPU可以使用访问内存的指令访问设备的内存空间(host bridge 判断访问物理内存还是设备中的内存),此时我们就可以像访问内存一样来访问设备的内存(寄存器)!

现在我们就可以使用 readl() 或writel() 函数读取或写入 EHCI 中 Capability  Registers Operational Registers,此时我们就可以对EHCI 编程了!

图:
阅读(2554) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~