- 论坛徽章:
- 0
|
本帖最后由 zd零 于 2011-04-08 09:31 编辑
最近一直在研究 USB 看到了 EHCI 部分,在usb_hcd_pci_probe () 函数中:
hcd->regs = ioremap_nocache (hcd->rsrc_start, hcd->rsrc_len);
ioremap_nocache()函数我想大家都不陌生,现在我就把此函数分析一下,不当之处请大家谅解,欢迎指正!
开始吧:
对于EHCI 来说它把它本身的寄存器和内存映射到内存中区!但是站在CPU的角度来说,我们无法直接访问这块内存空间,需要将设备的总线地址映射成一个cpu可访问的线性地址!
代码:
内核版本 2.6.22.1
cat /proc/iomem
此时我们就以此区间(0xd8426800 – 0xd8426bff)表示EHCI
注意:此时我们假设没有打开PAE!
void __iomem *ioremap_nocache (unsigned long phys_addr, unsigned long size)
{
phys_addr EHCI 总线地址
size 区间大小(1023)
unsigned long last_addr;
void __iomem *p = __ioremap(phys_addr, size, _PAGE_PCD);
#define _PAGE_PCD 0x010
if (!p)
return p;
/* Guaranteed to be > phys_addr, as per __ioremap() */
last_addr = phys_addr + size - 1;
if (last_addr < virt_to_phys(high_memory) - 1) {
struct page *ppage = virt_to_page(__va(phys_addr));
unsigned long npages;
phys_addr &= PAGE_MASK;
/* This might overflow and become zero.. */
last_addr = PAGE_ALIGN(last_addr);
/* .. but that's ok, because modulo-2**n arithmetic will make
* the page-aligned "last - first" come out right.
*/
npages = (last_addr - phys_addr) >> PAGE_SHIFT;
if (change_page_attr(ppage, npages, PAGE_KERNEL_NOCACHE) < 0) {
iounmap(p);
p = NULL;
}
global_flush_tlb();
}
return p;
}
非连续映射地址空间
非连续映射地址空间用于将连续的线性地址映射到不连续的物理地址!同时它也提供了一种访问高物理内存的方法!
内核主要在一下三种情况使用非连续映射地址空间
映射设备的I/O空间
为内核模块分配空间
为交换分区分配空间
非连续映射地址空间的起始地址在常规映射地址空间的结束地址后8MB-16MB之间,而且保证8MB对齐(地址的低24位为0)
void __iomem * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
void __iomem * addr;
struct vm_struct * area;
unsigned long offset, last_addr;
pgprot_t prot;
/* Don't allow wraparound or zero size */
last_addr = phys_addr + size - 1;
总线地址末端
if (!size || last_addr < phys_addr)
return NULL;
/*
* Don't remap the low PCI/ISA area, it's always mapped..
*/
if (phys_addr >= ISA_START_ADDRESS && last_addr < ISA_END_ADDRESS)
return (void __iomem *) phys_to_virt(phys_addr);
#define ISA_START_ADDRESS 0xa0000
#define ISA_END_ADDRESS 0x100000
640kb-1Mb 之间(此空洞用于连接到ISA总线上的设备)
/*
* Don't allow anybody to remap normal RAM that we're using..
*/
if (phys_addr <= virt_to_phys(high_memory - 1)) {
high_memory 为 896Mb 对应线性地址
phys_addr 在小于896Mb的常规内存空间中
char *t_addr, *t_end;
struct page *page;
t_addr = __va(phys_addr);
转化成线性地址
t_end = t_addr + (size - 1);
若小于896MB 则此页框应该被设置为保留
for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)
if(!PageReserved(page))
return NULL;
}
prot = __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY
| _PAGE_ACCESSED | flags);
#define __pgprot(x) ((pgprot_t) { (x) }
/*
* Mappings have to be page-aligned
*/
offset = phys_addr & ~PAGE_MASK;
取一页页框的偏移
phys_addr &= PAGE_MASK;
总线地址按4KB 对齐
#define PAGE_SHIFT 12
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#define PAGE_MASK (~(PAGE_SIZE-1))
size = PAGE_ALIGN(last_addr+1) - phys_addr;
#define PAGE_ALIGN(addr) (((addr)+PAGE_SIZE-1)&PAGE_MASK)
对齐操作
/*
* Ok, go for it..
*/
area = get_vm_area(size, VM_IOREMAP | (flags << 20));
#define VM_IOREMAP 0x00000001 /* ioremap() and friends */
申请一个非连续映射节点描述符
if (!area)
return NULL;
area->phys_addr = phys_addr;
总线地址
addr = (void __iomem *) area->addr;
起始线性地址
if (ioremap_page_range((unsigned long) addr,(unsigned long) addr + size, phys_addr, prot)) {
vunmap((void __force *) addr);
return NULL;
}
return (void __iomem *) (offset + (char __iomem *)addr);
offset + addr
offset 一般为0
addr 为线性地址,此地址被 CPU 用于读写 EHCI I/O mem 空间
这也验证ULK3那句话:在X86平台上总线地址就是物理地址,此时就可以通过访问内存的指令访问EHCI 的寄存器空间
}
struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)
{
return __get_vm_area(size, flags, VMALLOC_START, VMALLOC_END);
}
struct vm_struct *__get_vm_area(unsigned long size, unsigned long flags,unsigned long start, unsigned long end)
{
return __get_vm_area_node(size, flags, start, end, -1, GFP_KERNEL);
}
非连续映射地址空间
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 = area;
新申请的 struct vm_struct 加入到 vmlist 链表中
area->flags = flags;
area->addr = (void *)addr;
此时我们就假设 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=<size> to increase size.\n");
return NULL;
}
(页目录)
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))
页目录表中的偏移
init_mm.pgd = swapper_pg_dir
swapper_pg_dir 为页目录项地址
#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大小为 1023Kb,此处会循环设置
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 编程了!
图:
|
|