Chinaunix首页 | 论坛 | 博客
  • 博客访问: 47515
  • 博文数量: 34
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2014-08-18 21:23
文章分类

全部博文(34)

文章存档

2014年(34)

我的朋友

分类: LINUX

2014-08-18 21:30:12

1、为什么需要ioremap
问题:分配mmio(request_mem_region)后,得到的是物理地址,按理只需要将其转换为虚拟地址后(内核中,低端内存只是一个偏移而已),
应该就可以直接访问了吧?但是为什么还需要使用ioremap将其转换为线性地址后才能访问呢?
解答:分配的mmio的物理地址在内核中不一定能直接访问,比如:
1)x86 32位环境中,mmio的地址范围就在3G-4G之间,位于高端内存,内核中不能直接访问,需要进行映射。
2)一些体系架构中,IO内存根本就不能直接访问,必须建立相应的映射后才行,相应的映射工作由架构相关的ioremap完成
2、基本原理
ioremap完成相应的物理地址到内核空间中的线性地址(虚拟地址)的映射,并建立相应页表。
ioremap实际利用了vmalloc区中虚拟地址空间,因为内核虚拟地址空间实际已经分配完了:
3G--3G+896M为线性映射区,896-1024M为vmalloc、kmap和固定映射区
ioremap只能利用现有的地址空间,而能用的估计只有vmalloc区了,相对比较大。
ioremap实现的大致流程为:
__ioremap_caller
--> get_vm_area_caller // 在vmalloc区域中获取空闲的子区域。
      --> ioremap_page_range //修改页表,实现物理地址到虚拟地址的映射
3、代码分析

点击(此处)折叠或打开

  1. /*
  2.    * ioremap主处理函数,用于映射IO内存至内核虚拟
  3.    * 地址空间中,入参中包含了IO内存所在的物理
  4.    * 地址和长度,这个通常通过request_mem_region获得
  5.    * 返回值为映射后内核虚拟地址空间中的虚拟地址。
  6.    * 该返回地址实际位于vmalloc区中,但不实际分配
  7.    * 物理内存(vmalloc是需要分配物理内存的),使用
  8.    * vm_struct结构中phys_addr和addr实现虚拟地址到物理地址
  9.    * 间的映射。
  10.    */
  11. static void __iomem *__ioremap_caller(resource_size_t phys_addr,
  12.         unsigned long size, unsigned long prot_val, void *caller)
  13. {
  14.     unsigned long offset, vaddr;
  15.     resource_size_t pfn, last_pfn, last_addr;
  16.     const resource_size_t unaligned_phys_addr = phys_addr;
  17.     const unsigned long unaligned_size = size;
  18.     struct vm_struct *area;
  19.     unsigned long new_prot_val;
  20.     pgprot_t prot;
  21.     int retval;
  22.     void __iomem *ret_addr;

  23.     /* Don't allow wraparound or zero size */
  24.     // 防止地址过大发生翻转
  25.     last_addr = phys_addr + size - 1;
  26.     if (!size || last_addr < phys_addr)
  27.         return NULL;

  28.     // 判断物理地址是否有效
  29.     if (!phys_addr_valid(phys_addr)) {
  30.         printk(KERN_WARNING "ioremap: invalid physical address %llx\n",
  31.          (unsigned long long)phys_addr);
  32.         WARN_ON_ONCE(1);
  33.         return NULL;
  34.     }

  35.     /*
  36.      * Don't remap the low PCI/ISA area, it's always mapped..
  37.      */
  38.     /*
  39.      * 对于PCI/ISA区域(640k-16M),属于线性映射的区域
  40.      * 不需要再进行映射了。
  41.      */
  42.     if (is_ISA_range(phys_addr, last_addr))
  43.         return (__force void __iomem *)phys_to_virt(phys_addr);

  44.     /*
  45.      * Don't allow anybody to remap normal RAM that we're using..
  46.      */
  47.     /*
  48.      * 对于常规物理内存所在的区域,不能映射
  49.      * IO内存和常规物理内存在物理地址空间中的
  50.      * 分布在BIOS和内核初始化时就已经决定好了
  51.      */
  52.     last_pfn = last_addr >> PAGE_SHIFT;
  53.     for (pfn = phys_addr >> PAGE_SHIFT; pfn <= last_pfn; pfn++) {
  54.         int is_ram = page_is_ram(pfn);

  55.         if (is_ram && pfn_valid(pfn) && !PageReserved(pfn_to_page(pfn)))
  56.             return NULL;
  57.         WARN_ON_ONCE(is_ram);
  58.     }

  59.     /*
  60.      * Mappings have to be page-aligned
  61.      */
  62.     //页对齐处理
  63.     offset = phys_addr & ~PAGE_MASK;
  64.     phys_addr &= PHYSICAL_PAGE_MASK;
  65.     size = PAGE_ALIGN(last_addr+1) - phys_addr;

  66.     retval = reserve_memtype(phys_addr, (u64)phys_addr + size,
  67.                         prot_val, &new_prot_val);
  68.     if (retval) {
  69.         printk(KERN_ERR "ioremap reserve_memtype failed %d\n", retval);
  70.         return NULL;
  71.     }

  72.     if (prot_val != new_prot_val) {
  73.         if (!is_new_memtype_allowed(phys_addr, size,
  74.                      prot_val, new_prot_val)) {
  75.             printk(KERN_ERR
  76.         "ioremap error for 0x%llx-0x%llx, requested 0x%lx, got 0x%lx\n",
  77.                 (unsigned long long)phys_addr,
  78.                 (unsigned long long)(phys_addr + size),
  79.                 prot_val, new_prot_val);
  80.             goto err_free_memtype;
  81.         }
  82.         prot_val = new_prot_val;
  83.     }

  84.     switch (prot_val) {
  85.     case _PAGE_CACHE_UC:
  86.     default:
  87.         prot = PAGE_KERNEL_IO_NOCACHE;
  88.         break;
  89.     case _PAGE_CACHE_UC_MINUS:
  90.         prot = PAGE_KERNEL_IO_UC_MINUS;
  91.         break;
  92.     case _PAGE_CACHE_WC:
  93.         prot = PAGE_KERNEL_IO_WC;
  94.         break;
  95.     case _PAGE_CACHE_WB:
  96.         prot = PAGE_KERNEL_IO;
  97.         break;
  98.     }

  99.     /*
  100.      * Ok, go for it..
  101.      */
  102.     // 在vmalloc区域中获取空闲的子区域。
  103.     area = get_vm_area_caller(size, VM_IOREMAP, caller);
  104.     if (!area)
  105.         goto err_free_memtype;
  106.     /*
  107.      * 设置vm_struct->phys_addr为ioremap的物理地址
  108.      * 该字段的详细说明见vm_struct结构的定义
  109.      */
  110.     area->phys_addr = phys_addr;
  111.     //addr为该内存区域首部的虚拟地址
  112.     vaddr = (unsigned long) area->addr;

  113.     if (kernel_map_sync_memtype(phys_addr, size, prot_val))
  114.         goto err_free_area;

  115.     //修改页表,实现物理地址到虚拟地址的映射
  116.     if (ioremap_page_range(vaddr, vaddr + size, phys_addr, prot))
  117.         goto err_free_area;

  118.     ret_addr = (void __iomem *) (vaddr + offset);
  119.     mmiotrace_ioremap(unaligned_phys_addr, unaligned_size, ret_addr);

  120.     /*
  121.      * Check if the request spans more than any BAR in the iomem resource
  122.      * tree.
  123.      */
  124.     WARN_ONCE(iomem_map_sanity_check(unaligned_phys_addr, unaligned_size),
  125.          KERN_INFO "Info: mapping multiple BARs. Your kernel is fine.");

  126.     return ret_addr;
  127. err_free_area:
  128.     free_vm_area(area);
  129. err_free_memtype:
  130.     free_memtype(phys_addr, phys_addr + size);
  131.     return NULL;
  132. }





阅读(2213) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~