天天看點

核心代碼閱讀(11) - ioremap

外部裝置存儲空間的位址映射 ioremap

将外設裝置上的存儲位址反向映射到核心的虛拟位址空間。
裝置相關的,下面的 __ioremap 是i386的版本。      
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
    {
        void * addr;
        struct vm_struct * area;
        unsigned long offset, last_addr;
        last_addr = phys_addr + size - 1;
        if (!size || last_addr < phys_addr)
                return NULL;
        if (phys_addr >= 0xA0000 && last_addr < 0x100000)
                return phys_to_virt(phys_addr);
        if (phys_addr < virt_to_phys(high_memory)) {
                char *t_addr, *t_end;
                struct page *page;
                t_addr = __va(phys_addr);
                t_end = t_addr + (size - 1);
           
                for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)
                        if(!PageReserved(page))
                                return NULL;
        }
        offset = phys_addr & ~PAGE_MASK;
        phys_addr &= PAGE_MASK;
        size = PAGE_ALIGN(last_addr) - phys_addr;
        area = get_vm_area(size, VM_IOREMAP);
        if (!area)
                return NULL;
        addr = area->addr;
        if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags)) {
                vfree(addr);
                return NULL;
        }
        return (void *) (offset + (char *)addr);
    }      
1) 首先是一些sanity check。
2) last_addr = phys_addr + size - 1;
   if (!size || last_addr < phys_addr)
           return NULL;
   檢查映射區間大于0.
3) if (phys_addr >= 0xA0000 && last_addr < 0x100000)
           return phys_to_virt(phys_addr);
   VGA卡和BIOS的實體位址始終映射。無需ioremap.
4) if (phys_addr < virt_to_phys(high_memory))
   high_memory: 這個變量是在系統初始化的時候,檢測到記憶體條最大的實體位址所對應的虛拟位址。
   phys_addr 小于最大的位址,說明phys_addr和系統的記憶體位址沖突了。
5) if(!PageReserved(page))
   如果位址沖突了,檢查是否核心的虛拟位址空間留有空洞。
6) get_vm_area
   擷取一段核心中的空的虛拟位址。
7)         if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags))      

get_vm_area 核心擷取空閑的虛拟位址空間

struct vm_struct * get_vm_area(unsigned long size, unsigned long flags)
    {
        unsigned long addr;
        struct vm_struct **p, *tmp, *area;
        area = (struct vm_struct *) kmalloc(sizeof(*area), GFP_KERNEL);
        if (!area)
                return NULL;
        size += PAGE_SIZE;
        addr = VMALLOC_START;
        write_lock(&vmlist_lock);
        for (p = &vmlist; (tmp = *p) ; p = &tmp->next) {
                if ((size + addr) < addr) {
                        write_unlock(&vmlist_lock);
                        kfree(area);
                        return NULL;
                }
                if (size + addr < (unsigned long) tmp->addr)
                        break;
                addr = tmp->size + (unsigned long) tmp->addr;
                if (addr > VMALLOC_END-size) {
                        write_unlock(&vmlist_lock);
                        kfree(area);
                        return NULL;
                }
        }
        area->flags = flags;
        area->addr = (void *)addr;
        area->size = size;
        area->next = *p;
        *p = area;
        write_unlock(&vmlist_lock);
        return area;
    }      
1) struct vm_struct * vmlist;
   核心的為自己保留一個虛拟空間位址的隊列。
   struct vm_struct {
    unsigned long flags;
    void * addr;
    unsigned long size;
    struct vm_struct * next;
   };
2) size += PAGE_SIZE;
   每一段虛存空間後面跟一個空洞。
3) addr = VMALLOC_START;
   核心需要的虛拟位址空間是從 high_memory-8MB處開始。      

remap_area_pages 核心中的頁式映射

static int remap_area_pages(unsigned long address, unsigned long phys_addr,
                                 unsigned long size, unsigned long flags)
    {
        pgd_t * dir;
        unsigned long end = address + size;
        phys_addr -= address;
        dir = pgd_offset(&init_mm, address);
        flush_cache_all();
        if (address >= end)
                BUG();
        do {
                pmd_t *pmd;
                pmd = pmd_alloc_kernel(dir, address);
                if (!pmd)
                        return -ENOMEM;
                if (remap_area_pmd(pmd, address, end - address,
                                         phys_addr + address, flags))
                        return -ENOMEM;
                address = (address + PGDIR_SIZE) & PGDIR_MASK;
                dir++;
        } while (address && (address < end));
        flush_tlb_all();
        return 0;
    }      
1) 核心中沒有task_struct, 用init_mm描述核心的虛拟位址管理。
2) phys_addr -= address;
   在do-while循環中每次都要把實體位址的開始位址傳進去。
3) remap_area_pmd
   逐級映射pmd, pte      

繼續閱讀