分类: LINUX
2015-12-02 16:18:31
原文地址:ioremap_nocache 函数分析(二) 作者:zd零
非连续映射地址空间
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=
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 编程了!