天天看點

Linux驅動之ioremap源碼跟蹤

作者:有AI野心的電工和碼農
又一篇講老版本核心驅動的,多年沒寫過核心驅動了,也不知這些技術現在還适用不,權當紀念吧。
Original address:http://blog.chinaunix.net/uid-26009923-id-3291183.html

TQ2440的watchdog linux驅動在核心源碼linux-2.6.30.4的:./drivers/watchdog/s3c2410_wdt.c下

  1. watchdog ioremap的過程:
res->start=0x53000000   //實體位址
wdt_base=0xc5400000 //虛拟位址
wdt_base = ioremap(res->start, size);           

将實體位址res->start映射到了虛拟位址0xc5400000處,映射大小為:size=0x100000.

注: 不知道為什麼剛開始這個size=8, 後來就成了0x100000(1M)。

  1. arch/arm/include/asm/io.h中, 找到ioremap的定義:
//出現兩個ioremap
    #ifndef __arch_ioremap
    #define ioremap(cookie,size)        __arm_ioremap(cookie, size, MT_DEVICE)
    #define ioremap_nocache(cookie,size)    __arm_ioremap(cookie, size, MT_DEVICE)
    #define ioremap_cached(cookie,size)    __arm_ioremap(cookie, size, MT_DEVICE_CACHED)
    #define ioremap_wc(cookie,size)        __arm_ioremap(cookie, size, MT_DEVICE_WC)
    #define iounmap(cookie)            __iounmap(cookie)
    #else
    #define ioremap(cookie,size)        __arch_ioremap((cookie), (size), MT_DEVICE)
    #define ioremap_nocache(cookie,size)    __arch_ioremap((cookie), (size), MT_DEVICE)
    #define ioremap_cached(cookie,size)    __arch_ioremap((cookie), (size), MT_DEVICE_CACHED)
    #define ioremap_wc(cookie,size)        __arch_ioremap((cookie), (size), MT_DEVICE_WC)
    #define iounmap(cookie)            __arch_iounmap(cookie)
    #endif

    // 沒有定義 #ifndef __arch_ioremap,是以是
    #define MT_DEVICE        0
    #define ioremap(cookie,size)        __arm_ioremap(cookie, size, MT_DEVICE)           
  1. 在arch/arm/mm/ioremap.c中
/**
    __arm_ioremap(cookie, size, MT_DEVICE)也有兩個定義,
    一個是在nommu.c中,
    一個是在arch/arm/mm/ioremap.c中,是以很明顯進入arch/arm/mm/ioremap.c中:
    */

    //這個函數将實體位址phys_addr拆成兩部分高20位的頁幀号(Page Frame Number)pfn和低12位的頁内偏移位址offset
    void __iomem *
    __arm_ioremap(unsigned long phys_addr, size_t size, unsigned int mtype)
    {
        unsigned long last_addr;
         unsigned long offset = phys_addr & ~PAGE_MASK;
         unsigned long pfn = __phys_to_pfn(phys_addr);

         /*
          * Don't allow wraparound or zero size
         */
        last_addr = phys_addr + size - 1;
        if (!size || last_addr < phys_addr)
            return NULL;
         //此處列印值如下: last_addr=0x530fffff, size=0x100000, offset=0x0, pfn=0x53000
         return __arm_ioremap_pfn(pfn, offset, size, mtype);
    }
    EXPORT_SYMBOL(__arm_ioremap);

    /*
     * Convert a physical address to a Page Frame Number and back
     */
    #define PAGE_SHIFT        12
    #define    __phys_to_pfn(paddr)    ((paddr) >> PAGE_SHIFT)           
  1. 在arch/arm/mm/ioremap.c中
/*
     * Remap an arbitrary physical address space into the kernel virtual
     * address space. Needed when the kernel wants to access high addresses
     * directly.
     *
     * We need to allow non-page-aligned mappings too: we will obviously
     * have to convert them into an offset in a page-aligned mapping, but the
     * caller shouldn't need to know that small detail.
     *
     * 'flags' are the extra L_PTE_ flags that you want to specify for this
     * mapping. See <asm/pgtable.h> for more information.
     */
    void __iomem *
    __arm_ioremap_pfn(unsigned long pfn, unsigned long offset, size_t size,
             unsigned int mtype)
    {
        const struct mem_type *type;
        int err;
        unsigned long addr;
         struct vm_struct * area;

        /*
         * High mappings must be supersection aligned
         */
        if (pfn >= 0x100000 && (__pfn_to_phys(pfn) & ~SUPERSECTION_MASK))
            return NULL;

        type = get_mem_type(mtype);
        if (!type)
            return NULL;

        /*
         * Page align the mapping size, taking account of any offset.
         */
        size = PAGE_ALIGN(offset + size);

         area = get_vm_area(size, VM_IOREMAP); //配置設定虛拟位址空間
         if (!area)
             return NULL;
         addr = (unsigned long)area->addr;
         //此處列印出的位址: addr=0xc5400000
    #ifndef CONFIG_SMP //不進入此處
        if (DOMAIN_IO == 0 &&
         (((cpu_architecture() >= CPU_ARCH_ARMv6) && (get_cr() & CR_XP)) ||
         cpu_is_xsc3()) && pfn >= 0x100000 &&
         !((__pfn_to_phys(pfn) | size | addr) & ~SUPERSECTION_MASK)) {
            area->flags |= VM_ARM_SECTION_MAPPING;
            err = remap_area_supersections(addr, pfn, size, type);
        } else if (!((__pfn_to_phys(pfn) | size | addr) & ~PMD_MASK)) {
            area->flags |= VM_ARM_SECTION_MAPPING;
            err = remap_area_sections(addr, pfn, size, type);
        } else
    #endif
            err = remap_area_pages(addr, pfn, size, type);

        if (err) {
             vunmap((void *)addr);
             return NULL;
         }

        flush_cache_vmap(addr, addr + size);
        return (void __iomem *) (offset + addr);
    }
    EXPORT_SYMBOL(__arm_ioremap_pfn);           

area = get_vm_area(size, VM_IOREMAP); //配置設定虛拟位址空間

err = remap_area_pages(addr, pfn, size, type);

這兩個函數。

  1. 在arch/arm/mm/ioremap.c中
static int
    remap_area_sections(unsigned long virt, unsigned long pfn,
             size_t size, const struct mem_type *type)
    {
        unsigned long addr = virt, end = virt + size;
        pgd_t *pgd;

        /*
         * Remove and free any PTE-based mapping, and
         * sync the current kernel mapping.
         */
        unmap_area_sections(virt, size);

        pgd = pgd_offset_k(addr);
        do {
            pmd_t *pmd = pmd_offset(pgd, addr);

            pmd[0] = __pmd(__pfn_to_phys(pfn) | type->prot_sect);
            pfn += SZ_1M >> PAGE_SHIFT;
            pmd[1] = __pmd(__pfn_to_phys(pfn) | type->prot_sect);
            pfn += SZ_1M >> PAGE_SHIFT;
            flush_pmd_entry(pmd);

            addr += PGDIR_SIZE;
            pgd++;
        } while (addr < end);

        return 0;
    }