非连续映射地址空间

`</p>

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;

}`</pre> 

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

(页目录)
  
`</p>
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 内存 (页上层目录) `</p>
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;

}
(页中间目录) `</p>
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;

}`
(页表) 开始干正经事了 `</p>
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;

}`
`</p>
#define pte_alloc_kernel(pmd, address)                  \
        ((unlikely(!pmd_present(*(pmd))) && __pte_alloc_kernel(pmd, address))? \
                NULL: pte_offset_kernel(pmd, address))`
`</p>
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;

}`
`</p>
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);

}`
`</p>
#define pte_offset_kernel(dir, address) \
       ((pte_t *) pmd_page_vaddr(*(dir)) +  pte_index(address))`
> 线性地址 addr 在页表中的地址 `</p>
#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位 `</p>
#define PTRS_PER_PTE    1024`
调用 ioremap_nocache() 函数之后,返回一个线性地址,此时 CPU 可以访问设备的内存(已经将其映射到了线性地址空间中了),此时 CPU 可以使用访问内存的指令访问设备的内存空间(host bridge 判断访问物理内存还是设备中的内存),此时我们就可以像访问内存一样来访问设备的内存(寄存器)! 现在我们就可以使用 readl() 或 writel() 函数读取或写入 EHCI 中 Capability Registers Operational Registers,此时我们就可以对EHCI 编程了! 注意: PCI 设备的 I/O 寄存器 与 配置寄存器 的区别哦! 只提一点,后续的文章会有一部分专门介绍 PCI 对于任何一个PCI 设备 其内部 都有 I/O 寄存器 \----> 由cpu 直接访问 (writel or readl) PCI 配置寄存器 \----> host bridge 直接访问 (pci\_read\_config_word) 真相在此: