Linux内核访问外设I

2019-10-14 20:09栏目:网络时代
TAG:

  大家清楚默许外设I/O能源是不在Linux内核空间中的(如sram或硬件接口存放器等),若须要拜望该外设I/O能源,必得先将其地址映射到根本空间中来,然后技能在基本空间中拜谒它。

转自:

转自:

  Linux内核访谈外设I/O内部存款和储蓄器财富的法门有三种:动态映射(ioremap)和静态映射(map_desc)。

内存映射

1. 简介

  内核提供了三个第一的构造体struct machine_desc ,这么些结构体在基础移植中起到十分关键的成效,内核通过machine_desc结构体来调节种类系统架构相关部分的伊始化。

       machine_desc结构体通过MACHINE_START宏来开端化,在代码中, 通过在start_kernel->setup_arch中调用setup_machine_fdt来获取。

 

  一、动态映射(ioremap)格局

    对于提供了MMU(存款和储蓄处理器,扶持操作系统实行内部存款和储蓄器管理,提供虚实地址调换等硬件支撑)的计算机来讲,Linux提供了复杂的存款和储蓄管理系统,使得进程所能访谈的内部存款和储蓄器达到4GB。

2. machine_desc结构体

         machine_desc结构体定义如下:

 

[cpp] view plaincopy

 

  1. /* 在文件:arch/arm/include/asm/mach/arch.h */  
  2. struct machine_desc {  
  3.     unsigned int        nr;     /* architecture number  */  
  4.     const char      *name;      /* architecture name    */  
  5.     unsigned long       boot_params;    /* tagged list      */  
  6.     const char      **dt_compat;    /* array of device tree 
  7.                          * 'compatible' strings */  
  8.   
  9.     unsigned int        nr_irqs;    /* number of IRQs */  
  10.   
  11.     unsigned int        video_start;    /* start of video RAM   */  
  12.     unsigned int        video_end;  /* end of video RAM */  
  13.   
  14.     unsigned int        reserve_lp0 :1; /* never has lp0    */  
  15.     unsigned int        reserve_lp1 :1; /* never has lp1    */  
  16.     unsigned int        reserve_lp2 :1; /* never has lp2    */  
  17.     unsigned int        soft_reboot :1; /* soft reboot      */  
  18.     void            (*fixup)(struct machine_desc *,  
  19.                      struct tag *, char **,  
  20.                      struct meminfo *);  
  21.     void            (*reserve)(void);/* reserve mem blocks  */  
  22.     void            (*map_io)(void);/* IO mapping function  */  
  23.     void            (*init_early)(void);  
  24.     void            (*init_irq)(void);  
  25.     struct sys_timer    *timer;     /* system tick timer    */  
  26.     void            (*init_machine)(void);  
  27. #ifdef CONFIG_MULTI_IRQ_HANDLER  
  28.     void            (*handle_irq)(struct pt_regs *);  
  29. #endif  
  30. };  

 

  动态映射情势是豪门使用了比非常多的,也相比轻松。即直接通过基础提供的ioremap函数动态创造一段外设I/O内部存款和储蓄器财富到根本设想地址的映射表,从而得以在基本空间中拜访这段I/O能源。

  进度的4GB内部存款和储蓄器空间被人为的分成多少个部分--客户空间与基本空间。客商空间地址布满从0到3GB(PAGE_OFFSET,在0x86中它等于0xC0000000),3GB到4GB为水源空间,如下图:

 3. machine_desc初始化

machine_desc最初化例子如下:

[cpp] view plaincopy

 

  1. MACHINE_START(MA, "myboard")  
  2.     .boot_params    = PLAT_PHYS_OFFSET + 0x800,  
  3.     .fixup      = ma_fixup,  
  4.     .reserve             = &ma_reserve,  //内设有交给Linux管理在此以前,实行预先留下  
  5.     .map_io     = ma_map_io,  
  6.     .init_irq   = ma_init_irq,  
  7.     .timer      = &ma_timer,  
  8.     .init_machine   = machine_ma_board_init,  
  9. MACHINE_END  

MACHINE_START和MACHINE_END宏定义如下:

[cpp] view plaincopy

 

  1. /* 
  2.  * Set of macros to define architecture features.  This is built into 
  3.  * a table by the linker. 
  4.  */  
  5. #define MACHINE_START(_type,_name)            
  6. static const struct machine_desc __mach_desc_##_type      
  7.  __used                           
  8.  __attribute__((__section__(".arch.info.init"))) = {      
  9.     .nr     = MACH_TYPE_##_type,          
  10.     .name       = _name,  
  11.   
  12. #define MACHINE_END               
  13. };  

  Ioremap宏定义在asm/io.h内:

  内核空间中,从3G到vmalloc_start这段地址是情理内部存款和储蓄器映射区域(该区域中蕴涵了内核镜像、物理页框表mem_map等等),比如大家利用的VMware设想系统内部存款和储蓄器是160M,那么3G~3G+160M那片内部存款和储蓄器就应当映射物理内部存款和储蓄器。在物理内部存款和储蓄器映射区之后,就是vmalloc区域。对于 160M的系统来讲,vmalloc_start地点应在3G+160M左近(在情理内存映射区与vmalloc_start时期还存在多少个8M的gap 来幸免跃界),vmalloc_end的岗位临近4G(最终地方系统会保留一片128k大小的区域用来专项使用页面映射),如下图:

  #define ioremap(cookie,size)           __ioremap(cookie,size,0)

  kmalloc和get_free_page申请的内部存储器位于物理内存映射区域,况兼在大意上也是接连的,它们与诚实的情理地址独有贰个定点的偏移,因而存在较轻易的改造关系,virt_to_phys()能够兑现基础虚构地址转化为大意地址:

  __ioremap函数原型为(arm/mm/ioremap.c):

#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)

  void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long

extern inline unsigned long virt_to_phys(volatile void * address)

  flags);

{

  phys_addr:要映射的发端的IO地址

 return __pa(address);

  size:要映射的半空中的大大小小

}

  flags:要映射的IO空间和权限有关的标记

  上边调换进程是将虚构地址减去3G(PAGE_OFFSET=0XC000000)。

  该函数再次回到映射后的基本虚构地址(3G-4G). 接着便足以经过读写该重临的基础虚构地址去寻访之这段I/O内部存款和储蓄器能源。

  与之对应的函数为phys_to_virt(),将根本物理地址转化为设想地址:

  举贰个轻松易行的事例: (取自s3c2410的iis音频驱动)

#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))

  举个例子我们要拜候s3c2410平台上的I2S寄放器, 查看datasheet 知道IIS物理地址为0x5陆仟000,

extern inline void * phys_to_virt(unsigned long address)

  大家把它定义为宏S3C2410_PA_IIS,如下:

{

  #define S3C2410_PA_IIS    (0x55000000)

 return __va(address);

  若要在基础空间(iis驱动)中访谈这段I/O寄放器(IIS)能源需求先创制到基本地址空间的照耀:

}

  our_card->regs = ioremap(S3C2410_PA_IIS, 0x100);

  virt_to_phys()和phys_to_virt()都定义在include\asm-i386\io.h中。

  if (our_card->regs == NULL) {

  而vmalloc申请的内部存款和储蓄器则放在vmalloc_start~vmalloc_end之间,与物理地址没有简单的转变关系,固然在逻辑上它们也是延续的,然而在情理上它们不供给延续。

  err = -ENXIO;

  大家用下边包车型大巴次第来演示kmalloc、get_free_page和vmalloc的区别:

  goto exit_err;

#include 

  }

#include 

  成立好了后头,大家就能够透过readl(our_card->regs )或writel(value, our_card->regs)等IO接口函数去拜候它。

#include 

  二、静态映射(map_desc)方式

MODULE_LICENSE("GPL"); 

  上面着重介绍静态映射情势即透过map_desc结构体静态创制I/O资源映射表。

unsigned char *pagemem;

  内核提供了在系统运维时经过map_desc结构体静态创设I/O财富到基当地址空间的线性映射表(即page table)的点子,这种映射表是一种一一映射的关系。程序猿能够本身定义该I/O内部存款和储蓄器财富映射后的设想地址。创设好了静态映射表,在基础或驱动中访谈该I/O能源时则不需求再开展ioreamp动态映射,可以直接通过照射后的I/O设想地址去做客它。

unsigned char *kmallocmem;

  上面详细分析这种机制的规律并比如表明怎么着通过这种静态映射的点子访谈外设I/O内部存款和储蓄器能源。

unsigned char *vmallocmem;

  内核提供了八个第一的结构体struct machine_desc ,这一个结构体在基础移植中起到一定关键的功力,内核通过machine_desc结构体来调控系列系统架构相关部分的起头化。

int __init mem_module_init(void)

  machine_desc结构体的成员饱含了系统架构相关部分的多少个最器重的最初化函数,包含map_io,init_irq, init_machine以及phys_io , timer成员等。

{

  machine_desc结构体定义如下:

 //最棒每趟内部存款和储蓄器申请都检查申请是还是不是中标

  struct machine_desc {

 //下边这段仅仅看做示范的代码未有检查

  /*

 pagemem = (unsigned char*)get_free_page(0);

  * Note! The first four elements are used

 printk("pagemem addr=%x", pagemem);

  * by assembler code in head-armv.S

 kmallocmem = (unsigned char*)kmalloc(100, 0);

  */

 printk("kmallocmem addr=%x", kmallocmem);

  unsigned int        nr;        /* architecture number    */

 vmallocmem = (unsigned char*)vmalloc(1000000);

  unsigned int        phys_io;    /* start of physical io    */

 printk("vmallocmem addr=%x", vmallocmem);

  unsigned int        io_pg_offst;    /* byte offset for io

 return 0;

  * page tabe entry    */

}

  const char        *name;        /* architecture name    */

void __exit mem_module_exit(void)

  unsigned long        boot_params;    /* tagged list        */

{

  unsigned int        video_start;    /* start of video RAM    */

 free_page(pagemem);

  unsigned int        video_end;    /* end of video RAM    */

 kfree(kmallocmem);

  unsigned int        reserve_lp0 :1;    /* never has lp0    */

 vfree(vmallocmem);

  unsigned int        reserve_lp1 :1;    /* never has lp1    */

}

  unsigned int        reserve_lp2 :1;    /* never has lp2    */

module_init(mem_module_init);

  unsigned int        soft_reboot :1;    /* soft reboot        */

module_exit(mem_module_exit);

  void            (*fixup)(struct machine_desc *,

  大家的系统上有160MB的内部存款和储蓄器空间,运维一遍上述顺序,发掘pagemem的地址在0xc7ArrayArray九千(约3G+121M)、kmallocmem 地址在0xcArraybc1380(约3G+155M)、vmallocmem的地点在0xcabeb000(约3G+171M)处,切合前文所述的内部存款和储蓄器布局。

  struct tag *, char **,

  接下去,大家谈谈Linux设备驱动毕竟什么样访谈外设的I/O端口(贮存器)。

  struct meminfo *);

  差十分的少各个外设都以因而读写设备上的贮存器来张开的,经常饱含决定贮存器、状态寄放器和数据贮存器三大类,外设的存放器平时被连接地编址。依照CPU种类布局的两样,CPU对IO端口的编址方式有二种:

  void            (*map_io)(void);/* IO mapping function    */

  (1)I/O映射格局(I/O-mapped)

  void            (*init_irq)(void);

  规范地,如X86管理器为外设特意完成了八个单独的地方空间,称为"I/O地址空间"恐怕"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来会见这一空中中的地址单元。

  struct sys_timer    *timer;        /* system tick timer    */

(2)内部存款和储蓄器映射方式(Memory-mapped)

  void            (*init_machine)(void);

  ENCOREISC指令系统的CPU(如ARM、PowerPC等)日常只兑现二个物理地址空间,外设I/O端口成为内存的一有的。此时,CPU能够象访问三个内存单元那样访谈外设I/O端口,而无需设置特意的外设I/O指令。

  };

  可是,这两个在硬件达成上的差距对于软件来讲是全然透明的,驱动程序开采人士能够将内部存款和储蓄器映射情势的I/O端口和外设内部存款和储蓄器统一作为是"I/O内部存款和储蓄器"能源。

图片 1

  日常的话,在系统运维时,外设的I/O内部存款和储蓄器财富的物理地址是已知的,由硬件的规划决定。不过CPU平日并从未为这个已知的外设I/O内部存款和储蓄器能源的情理地址预订义虚构地址范围,驱动程序并无法直接通过物理地址访谈I/O内部存款和储蓄器能源,而必须将它们映射到骨干虚地址空间内(通过页表),然后本事依赖映射所收获的主导虚地址范围,通过访内指令访谈这么些I/O内部存款和储蓄器财富。Linux在io.h头文件中宣称了函数ioremap(),用来将I/O内存财富的物理地址映射到骨干虚地址空间(3GB-4GB)中,原型如下:

void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);

  iounmap函数用于裁撤ioremap()所做的照耀,原型如下:

void iounmap(void * addr);

  那多少个函数都以贯彻在mm/ioremap.c文件中。

  在将I/O内存财富的物理地址映射成人中学央虚地址后,理论上讲大家就足以象读写RAM那样直接读写I/O内部存款和储蓄器财富了。为了保障驱动程序的跨平台的可移植性,大家应当利用Linux中一定的函数来拜候I/O内部存储器财富,而不该通过指向宗旨虚地址的指针来拜候。如在x86平台上,读写I/O的函数如下所示:

#define readb(addr) (*(volatile unsigned char *) __io_virt(addr))

#define readw(addr) (*(volatile unsigned short *) __io_virt(addr))

#define readl(addr) (*(volatile unsigned int *) __io_virt(addr))

#define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))

#define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))

#define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))

#define memset_io(a,b,c) memset(__io_virt(a),(b),(c))

#define memcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))

#define memcpy_toio(a,b,c) memcpy(__io_virt(a),(b),(c))

  最后,我们要极其强调驱动程序中mmap函数的落真实情况势。用mmap映射壹个装置,意味着使客商空间的一段地址关联到器具内存上,那使得只要程序在分配的地方范围内实行读取恐怕写入,实际上正是对设施的访问。

  小编在Linux源代码中开展包涵"ioremap"文本的检索,发掘确实出现的ioremap的地点一定少。所以作者追根索源地找寻I/O操作的物理地址转变来设想地址的忠实所在,开采Linux有顶替ioremap的口舌,可是这些调换进程却是不可缺少的。

  举例我们再一次选拔S3C2410那一个ARM晶片RTC(实石英钟)驱动中的一小段:

static void get_rtc_time(int alm, struct rtc_time *rtc_tm)

{

 spin_lock_irq(&rtc_lock);

 if (alm == 1) {

  rtc_tm->tm_year = (unsigned char)ALMYEAR & Msk_RTCYEAR;

  rtc_tm->tm_mon = (unsigned char)ALMMON & Msk_RTCMON;

  rtc_tm->tm_mday = (unsigned char)ALMDAY & Msk_RTCDAY;

  rtc_tm->tm_hour = (unsigned char)ALMHOUR & Msk_RTCHOUR;

  rtc_tm->tm_min = (unsigned char)ALMMIN & Msk_RTCMIN;

  rtc_tm->tm_sec = (unsigned char)ALMSEC & Msk_RTCSEC;

 }

 else {

  read_rtc_bcd_time:

  rtc_tm->tm_year = (unsigned char)BCDYEAR & Msk_RTCYEAR;

  rtc_tm->tm_mon = (unsigned char)BCDMON & Msk_RTCMON;

  rtc_tm->tm_mday = (unsigned char)BCDDAY & Msk_RTCDAY;

  rtc_tm->tm_hour = (unsigned char)BCDHOUR & Msk_RTCHOUR;

  rtc_tm->tm_min = (unsigned char)BCDMIN & Msk_RTCMIN;

  rtc_tm->tm_sec = (unsigned char)BCDSEC & Msk_RTCSEC;

  if (rtc_tm->tm_sec == 0) {

   /* Re-read all BCD registers in case of BCDSEC is 0.

   See RTC section at the manual for more info. */

   goto read_rtc_bcd_time;

  }

 }

 spin_unlock_irq(&rtc_lock);

 BCD_TO_BIN(rtc_tm->tm_year);

 BCD_TO_BIN(rtc_tm->tm_mon);

 BCD_TO_BIN(rtc_tm->tm_mday);

 BCD_TO_BIN(rtc_tm->tm_hour);

 BCD_TO_BIN(rtc_tm->tm_min);

 BCD_TO_BIN(rtc_tm->tm_sec);

 /* The epoch of tm_year is 1Array00 */

 rtc_tm->tm_year += RTC_LEAP_YEAR - 1Array00;

 /* tm_mon starts at 0, but rtc month starts at 1 */

 rtc_tm->tm_mon--;

}

  I/O操作就像是正是对ALMYEACR-V、ALMMON、ALMDAY定义的存放器举办操作,那那些宏终归定义为啥吧?

#define ALMDAY bRTC(0x60)

#define ALMMON bRTC(0x64)

#define ALMYEAR bRTC(0x68)

  个中依附了宏bRTC,那些宏定义为:

#define bRTC(Nb) __REG(0x57000000 + (Nb))

  在那之中又依附了宏__REG,而__REG又定义为:

# define __REG(x) io_p2v(x)

  最后的io_p2v才是的确"玩"虚构地址和情理地址转变的地方: 

#define io_p2v(x) ((x) | 0xa0000000)

  与__REG对应的有个__PREG:

# define __PREG(x) io_v2p(x)

  与io_p2v对应的有个io_v2p:

#define io_v2p(x) ((x) & ~0xa0000000)

  可知有未有出现ioremap是次要的,关键难题是有无虚拟地址和大要地址的转移!

上面包车型地铁顺序在开发银行的时候保留一段内部存款和储蓄器,然后利用ioremap将它映射到基本虚构空间,同期又用remap_page_range映射到客商虚构空间,这样一来,内核和客户都能访谈。借使在基本设想地址将这段内部存款和储蓄器带头化串"abcd",那么在客户虚构地址可以读出来:

/************mmap_ioremap.c**************/

#include 

#include 

#include 

#include 

#include  /* for mem_map_(un)reserve */

#include  /* for virt_to_phys */

#include  /* for kmalloc and kfree */

MODULE_PARM(mem_start, "i");

MODULE_PARM(mem_size, "i");

static int mem_start = 101, mem_size = 10;

static char *reserve_virt_addr;

static int major;

int mmapdrv_open(struct inode *inode, struct file *file);

int mmapdrv_release(struct inode *inode, struct file *file);

int mmapdrv_mmap(struct file *file, struct vm_area_struct *vma);

static struct file_operations mmapdrv_fops =

{

 owner: THIS_MODULE, mmap: mmapdrv_mmap, open: mmapdrv_open, release:

 mmapdrv_release,

};

int init_module(void)

{

 if ((major = register_chrdev(0, "mmapdrv", &mmapdrv_fops)) vm_pgoff vm_end - vma->vm_start;

 if (size > mem_size *1024 * 1024)

 {

  printk("size too big\n");

  return ( - ENXIO);

 }

 offset = offset + mem_start * 1024 * 1024;

 /* we do not want to have this area swapped out, lock it */

 vma->vm_flags |= VM_LOCKED;

 if (remap_page_range(vma, vma->vm_start, offset, size, PAGE_SHARED))

 {

  printk("remap page range failed\n");

  return - ENXIO;

 }

 return (0);

}

  remap_page_range函数的机能是布局用于映射一段物理地址的新页表,完结了基础空间与顾客空间的酷炫,其原型如下: 

int remap_page_range(vma_area_struct *vma, unsigned long from, unsigned long to, unsigned long size, pgprot_tprot); 

  使用mmap最特异的事例是呈现卡的驱动,将显存空间直接从内核映射到顾客空间将可提供显存的读写效用。

版权声明:本文由澳门新葡亰平台游戏发布于网络时代,转载请注明出处:Linux内核访问外设I