• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • 【轉帖】Linux對I/O端口資源的管理 (2)

    發表于:2007-05-26來源:作者:點擊數: 標簽:
    3.3 管理I/O Region資源 Linux 將基于I/O映射方式的I/O端口和基于內存映射方式的I/O端口資源統稱為I/O區域(I/O Region)。I/O Region仍然是一種I/O資源,因此它仍然可以用resource結構類型來描述。下面我們就來看看Linux是如何管理I/O Region的。 3.3.1
    clearcase/" target="_blank" >cccccc">3.3 管理I/O Region資源
      
        Linux將基于I/O映射方式的I/O端口和基于內存映射方式的I/O端口資源統稱為“I/O區域”(I/O Region)。I/O Region仍然是一種I/O資源,因此它仍然可以用resource結構類型來描述。下面我們就來看看Linux是如何管理I/O Region的。
      
        3.3.1 I/O Region的分配
      
        在函數__request_resource()的基礎上,Linux實現了用于分配I/O區域的函數__request_region(),如下:
      
      
      struct resource * __request_region(struct resource *parent,
        unsigned long start, unsigned long n, const char *name)
      {
      struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);
      
      if (res) {
      memset(res, 0, sizeof(*res));
      res->name = name;
      res->start = start;
      res->end = start + n - 1;
      res->flags = IORESOURCE_BUSY;
      
      write_lock(&resource_lock);
      
      for (;;) {
      struct resource *conflict;
      
      conflict = __request_resource(parent, res);
      if (!conflict)
      break;
      if (conflict != parent) {
      parent = conflict;
      if (!(conflict->flags & IORESOURCE_BUSY))
      continue;
      }
      
      /* Uhhuh, that didn't work out.. */
      kfree(res);
      res = NULL;
      break;
      }
      write_unlock(&resource_lock);
      }
      return res;
      }
      
      
      
      NOTE:
      
       ?、偈紫?,調用kmalloc()函數在SLAB分配器緩存中分配一個resource結構。
      
       ?、谌缓?,相應的根據參數?**跏薊??峙淶膔esource結構。注意!flags成員被初始化為IORESOURCE_BUSY。
      
       ?、劢酉聛?,用一個for循環開始進行資源分配,循環體的步驟如下:
      
        l 首先,調用__request_resource()函數進行資源分配。如果返回NULL,說明分配成功,因此就執行break語句推出for循環,返回所分配的resource結構的指針,函數成功地結束。
      
        l 如果__request_resource()函數分配不成功,則進一步判斷所返回的沖突資源節點是否就是父資源節點parent。如果不是,則將分配行為下降一個層次,即試圖在當前沖突的資源節點中進行分配(只有在沖突的資源節點沒有設置IORESOURCE_BUSY的情況下才可以),于是讓 parent指針等于conflict,并在conflict->flags&IORESOURCE_BUSY為0的情況下執行 continue語句繼續for循環。
      
        l 否則如果相沖突的資源節點就是父節點parent,或者相沖突資源節點設置了IORESOURCE_BUSY標志位,則宣告分配失敗。于是調用kfree ()函數釋放所分配的resource結構,并將res指針置為NULL,最后用break語句推出for循環。
      
       ?、茏詈?,返回所分配的resource結構的指針。
      
        3.3.2 I/O Region的釋放
      
        函數__release_region()實現在一個父資源節點parent中釋放給定范圍的I/O Region。實際上該函數的實現思想與__release_resource()相類似。其源代碼如下:
      
      
      void __release_region(struct resource *parent,
          unsigned long start, unsigned long n)
      {
      struct resource **p;
      unsigned long end;
      
      p = &parent->child;
      end = start + n - 1;
      
      for (;;) {
      struct resource *res = *p;
      
      if (!res)
      break;
      if (res->start <= start && res->end >= end) {
      if (!(res->flags & IORESOURCE_BUSY)) {
      p = &res->child;
      continue;
      }
      if (res->start != start' 'res->end != end)
      break;
      *p = res->sibling;
      kfree(res);
      return;
      }
      p = &res->sibling;
      }
      printk(Trying to free nonexistent resource <%08lx-%08lx>
      , start, end);
      }
      
      
      
        類似地,該函數也是通過一個for循環來遍歷父資源parent的child鏈表。為此,它讓指針res指向當前正被掃描的子資源節點,指針p指向前一個子資源節點的sibling成員變量,p的初始值為指向parent->child。For循環體的步驟如下:
      
       ?、僮宺es指針指向當前被掃描的子資源節點(res=*p)。
      
       ?、谌绻鹯es指針為NULL,說明已經掃描完整個child鏈表,所以退出for循環。
      
       ?、廴绻鹯es指針不為NULL,則繼續看看所指定的I/O區域范圍是否完全包含在當前資源節點中,也即看看[start,start+n-1]是否包含在res->[start,end]中。如果不屬于,則讓p指向當前資源節點的sibling成員,然后繼續for循環。如果屬于,則執行下列步驟:
      
        l 先看看當前資源節點是否設置了IORESOURCE_BUSY標志位。如果沒有設置該標志位,則說明該資源節點下面可能還會有子節點,因此將掃描過程下降一個層次,于是修改p指針,使它指向res->child,然后執行continue語句繼續for循環。
      
        l 如果設置了IORESOURCE_BUSY標志位。則一定要確保當前資源節點就是所指定的I/O區域,然后將當前資源節點從其父資源的child鏈表中去除。這可以通過讓前一個兄弟資源節點的sibling指針指向當前資源節點的下一個兄弟資源節點來實現(即讓*p=res->sibling),最后調用kfree()函數釋放當前資源節點的resource結構。然后函數就可以成功返回了。
      
        3.3.3 檢查指定的I/O Region是否已被占用
      
        函數__check_region()檢查指定的I/O Region是否已被占用。其源代碼如下:
      
      
      int __check_region(struct resource *parent, unsigned long start, unsigned long n)
      {
      struct resource * res;
      
      res = __request_region(parent, start, n, check-region);
      if (!res)
      return -EBUSY;
      
      release_resource(res);
      kfree(res);
      return 0;
      }
      
      
      
        該函數的實現與__check_resource()的實現思想類似。首先,它通過調用__request_region()函數試圖在父資源 parent中分配指定的I/O Region。如果分配不成功,將返回NULL,因此此時函數返回錯誤值-EBUSY表示所指定的I/O Region已被占用。如果res指針不為空則說明所指定的I/O Region沒有被占用。于是調用__release_resource()函數將剛剛分配的資源釋放掉(實際上是將res結構從parent的 child鏈表去除),然后調用kfree()函數釋放res結構所占用的內存。最后,返回0值表示指定的I/O Region沒有被占用。
      
      3.4 管理I/O端口資源
      
        我們都知道,采用I/O映射方式的X86處理器為外設實現了一個單獨的地址空間,也即“I/O空間”(I/O Space)或稱為“I/O端口空間”,其大小是64KB(0x0000-0xffff)。Linux在其所支持的所有平臺上都實現了“I/O端口空間” 這一概念。
      
        由于I/O空間非常小,因此即使外設總線有一個單獨的I/O端口空間,卻也不是所有的外設都將其I/O端口(指寄存器)映射到“I/O端口空間”中。比如,大多數PCI卡都通過內存映射方式來將其I/O端口或外設內存映射到CPU的RAM物理地址空間中。而老式的 ISA卡通常將其I/O端口映射到I/O端口空間中。
      
        Linux是基于“I/O Region”這一概念來實現對I/O端口資源(I/O-mapped 或 Memory-mapped)的管理的。
      
        3.4.1 資源根節點的定義
      
        Linux在kernel/Resource.c文件中定義了全局變量ioport_resource和iomem_resource,來分別描述基于I/O映射方式的整個I/O端口空間和基于內存映射方式的I/O內存資源空間(包括I/O端口和外設內存)。其定義如下:
      
      
      struct resource ioport_resource =
          { PCI IO, 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO };
      struct resource iomem_resource =
          { PCI mem, 0x00000000, 0xffffffff, IORESOURCE_MEM };
      
      
      
        其中,宏IO_SPACE_LIMIT表示整個I/O空間的大小,對于X86平臺而言,它是0xffff(定義在include/asm-i386/io.h頭文件中)。顯然,I/O內存空間的大小是4GB。
      
        3.4.2 對I/O端口空間的操作
      
        基于I/O Region的操作函數__XXX_region(),Linux在頭文件include/linux/ioport.h中定義了三個對I/O端口空間進行操作的宏:①request_region()宏,請求在I/O端口空間中分配指定范圍的I/O端口資源。②check_region()宏,檢查 I/O端口空間中的指定I/O端口資源是否已被占用。③release_region()宏,釋放I/O端口空間中的指定I/O端口資源。這三個宏的定義如下:
      
      
      #define request_region(start,n,name)
      __request_region(&ioport_resource, (start), (n), (name))
      #define check_region(start,n)
      __check_region(&ioport_resource, (start), (n))
      #define release_region(start,n)
      __release_region(&ioport_resource, (start), (n))
      
      
      
        其中,宏參數start指定I/O端口資源的起始物理地址(是I/O端口空間中的物理地址),宏參數n指定I/O端口資源的大小。
      
        3.4.3 對I/O內存資源的操作
      
        基于I/O Region的操作函數__XXX_region(),Linux在頭文件include/linux/ioport.h中定義了三個對I/O內存資源進行操作的宏:①request_mem_region()宏,請求分配指定的I/O內存資源。②check_ mem_region()宏,檢查指定的I/O內存資源是否已被占用。③release_ mem_region()宏,釋放指定的I/O內存資源。這三個宏的定義如下:
      
      
      #define request_mem_region(start,n,name)
        __request_region(&iomem_resource, (start), (n), (name))
      #define check_mem_region(start,n)
      __check_region(&iomem_resource, (start), (n))
      #define release_mem_region(start,n)
      __release_region(&iomem_resource, (start), (n))
      
      
      
        其中,參數start是I/O內存資源的起始物理地址(是CPU的RAM物理地址空間中的物理地址),參數n指定I/O內存資源的大小。
      
        3.4.4 對/proc/ioports和/proc/iomem的支持
      
        Linux在ioport.h頭文件中定義了兩個宏:
      
        get_ioport_list()和get_iomem_list(),分別用來實現/proc/ioports文件和/proc/iomem文件。其定義如下:
      
      
      #define get_ioport_list(buf) get_resource_list(&ioport_resource, buf, PAGE_SIZE)
      #define get_mem_list(buf) get_resource_list(&iomem_resource, buf, PAGE_SIZE)
      
      
      
      3.5 訪問I/O端口空間
      
        在驅動程序請求了I/O端口空間中的端口資源后,它就可以通過CPU的IO指定來讀寫這些I/O端口了。在讀寫I/O端口時要注意的一點就是,大多數平臺都區分8位、16位和32位的端口,也即要注意I/O端口的寬度。
      
        Linux在include/asm/io.h頭文件(對于i386平臺就是include/asm-i386/io.h)中定義了一系列讀寫不同寬度I/O端口的宏函數。如下所示:
      
       ?、抛x寫8位寬的I/O端口
      
      
        unsigned char inb(unsigned port);
        void outb(unsigned char value,unsigned port);
      
      
      
        其中,port參數指定I/O端口空間中的端口地址。在大多數平臺上(如x86)它都是unsigned short類型的,其它的一些平臺上則是unsigned int類型的。顯然,端口地址的類型是由I/O端口空間的大小來決定的。
      
       ?、谱x寫16位寬的I/O端口
      
      
        unsigned short inw(unsigned port);
        void outw(unsigned short value,unsigned port);
      
      
      
       ?、亲x寫32位寬的I/O端口
      
      
        unsigned int inl(unsigned port);
        void outl(unsigned int value,unsigned port);
      
      
      
        3.5.1 對I/O端口的字符串操作
      
        除了上述這些“單發”(single-shot)的I/O操作外,某些CPU也支持對某個I/O端口進行連續的讀寫操作,也即對單個I/O端口讀或寫一系列字節、字或32位整數,這就是所謂的“字符串I/O指令”(String Instruction)。這種指令在速度上顯然要比用循環來實現同樣的功能要快得多。
      
        Linux同樣在io.h文件中定義了字符串I/O讀寫函數:
      
       ?、?位寬的字符串I/O操作
      
      
        void insb(unsigned port,void * addr,unsigned long count);
        void outsb(unsigned port ,void * addr,unsigned long count);
      
      
      
       ?、?6位寬的字符串I/O操作
      
      
        void insw(unsigned port,void * addr,unsigned long count);
        void outsw(unsigned port ,void * addr,unsigned long count);
      
      
      
       ?、?2位寬的字符串I/O操作
      
      
        void insl(unsigned port,void * addr,unsigned long count);
        void outsl(unsigned port ,void * addr,unsigned long count);
      
      
      
        3.5.2 Pausing I/O
      
      
        在一些平臺上(典型地如X86),對于老式總線(如ISA)上的慢速外設來說,如果CPU讀寫其I/O端口的速度太快,那就可能會發生丟失數據的現象。對于這個問題的解決方法就是在兩次連續的I/O操作之間插入一段微小的時延,以便等待慢速外設。這就是所謂的“Pausing I/O”。
      
        對于Pausing I/O,Linux也在io.h頭文件中定義了它的I/O讀寫函數,而且都以XXX_p命名,比如:inb_p()、outb_p()等等。下面我們就以out_p()為例進行分析。
      
        將io.h中的宏定義__OUT(b,”b”char)展開后可得如下定義:
      
      
      extern inline void outb(unsigned char value, unsigned short port) {
      __asm__ __volatile__ (outb % b 0,% w 1
      : : a (value), Nd (port));
      }
      
      extern inline void outb_p(unsigned char value, unsigned short port) {
      __asm__ __volatile__ (outb % b 0,% w 1
      __FULL_SLOW_DOWN_IO
      : : a (value), Nd (port));
      }
      
      
      
        可以看出,outb_p()函數的實現中被插入了宏__FULL_SLOWN_DOWN_IO,以實現微小的延時。宏__FULL_SLOWN_DOWN_IO在頭文件io.h中一開始就被定義:
      
      
      #ifdef SLOW_IO_BY_JUMPING
      #define __SLOW_DOWN_IO
      jmp 1f
      1: jmp 1f
      1:
      #else
      #define __SLOW_DOWN_IO
      outb %%al,$0x80
      #endif
      
      #ifdef REALLY_SLOW_IO
      #define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
        __SLOW_DOWN_IO __SLOW_DOWN_IO __SLOW_DOWN_IO
      #else
      #define __FULL_SLOW_DOWN_IO __SLOW_DOWN_IO
      #endif
      
      
      
        顯然,__FULL_SLOW_DOWN_IO就是一個或四個__SLOW_DOWN_IO(根據是否定義了宏REALLY_SLOW_IO來決定),而宏__SLOW_DOWN_IO則被定義成毫無意義的跳轉語句或寫端口0x80的操作(根據是否定義了宏SLOW_IO_BY_JUMPING來決定)。
      
      3.6 訪問I/O內存資源
      
        盡管I/O端口空間曾一度在x86平臺上被廣泛使用,但是由于它非常小,因此大多數現代總線的設備都以內存映射方式(Memory-mapped)來映射它的I/O端口(指I/O寄存器)和外設內存?;趦却嬗成浞绞降腎/O端口(指I/O寄存器)和外設內存可以通稱為“I/O內存”資源(I/O Memory)。因為這兩者在硬件實現上的差異對于軟件來說是完全透明的,所以驅動程序開發人員可以將內存映射方式的I/O端口和外設內存統一看作是 “I/O內存”資源。
      
        從前幾節的闡述我們知道,I/O內存資源是在CPU的單一內存物理地址空間內進行編址的,也即它和系統RAM同處在一個物理地址空間內。因此通過CPU的訪內指令就可以訪問I/O內存資源。
      
        一般來說,在系統運行時,外設的I/O內存資源的物理地址是已知的,這可以通過系統固件(如BIOS)在啟動時分配得到,或者通過設備的硬連線(hardwired)得到。比如,PCI卡的I/O內存資源的物理地址就是在系統啟動時由PCI BIOS分配并寫到PCI卡的配置空間中的BAR中的。而ISA卡的I/O內存資源的物理地址則是通過設備硬連線映射到640KB-1MB范圍之內的。但是CPU通常并沒有為這些已知的外設I/O內存資源的物理地址預定義虛擬地址范圍,因為它們是在系統啟動后才已知的(某種意義上講是動態的),所以驅動程序并不能直接通過物理地址訪問I/O內存資源,而必須將它們映射到核心虛地址空間內(通過頁表),然后才能根據映射所得到的核心虛地址范圍,通過訪內指令訪問這些I/O內存資源。
      
        3.6.1 映射I/O內存資源
      
        Linux在io.h頭文件中聲明了函數ioremap(),用來將I/O內存資源的物理地址映射到核心虛地址空間(3GB-4GB)中,如下:
      
      
      void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
      void iounmap(void * addr);
      
       函數用于取消ioremap()所做的映射,參數addr是指向核心虛地址的指針。這兩個函數都是實現在mm/ioremap.c文件中。具體實現可參考《情景分析》一書。
      
        3.6.2 讀寫I/O內存資源
      
        在將I/O內存資源的物理地址映射成核心虛地址后,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O內存資源了。但是,由于在某些平臺上,對 I/O內存和系統內存有不同的訪問處理,因此為了確??缙脚_的兼容性,Linux實現了一系列讀寫I/O內存資源的函數,這些函數在不同的平臺上有不同的實現。但在x86平臺上,讀寫I/O內存與讀寫RAM無任何差別。如下所示(include/asm-i386/io.h):
      
      
      #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))
      
       上述定義中的宏__io_virt()僅僅檢查虛地址addr是否是核心空間中的虛地址。該宏在內核2.4.0中的實現是臨時性的。具體的實現函數在arch/i386/lib/Iodebug.c文件。
      
        顯然,在x86平臺上訪問I/O內存資源與訪問系統主存RAM是無差別的。但是為了保證驅動程序的跨平臺的可移植性,我們應該使用上面的函數來訪問I/O內存資源,而不應該通過指向核心虛地址的指針來訪問。

    原文轉自:http://www.kjueaiud.com

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>