Linux I / O Resources (ZZ)
3.1 Description of Linux to I / O resources
3.2 Linux management of I / O resources
3.2.1 Application for I / O Resources
3.2.2 Release of Resources
3.2.3 Check if resources have been occupied
3.2.4 Find available resources
3.2.5 Distribution Interface Allocate_Resource ()
3.2.6 Getting Resources Name List
3.3 Manage I / O Region resources
3.3.1 I / O Region Assignment
3.3.2 I / O Region release
3.3.3 Check if the specified I / O Region has been occupied
3.4 Manage I / O Port Resources
3.4.1 Definition of Resource Root Node
3.4.2 Operation of I / O port space
3.4.3 Operation of I / O Memory Resources
3.4.4 Support for / Proc / Ioports and / Proc / Iomem
3.5 Access I / O Port Space
3.5.1 String Operation on I / O Port
3.5.2 Pausing I / O
3.6 Access I / O Memory Resources
3.6.1 Mapping I / O Memory Resources
3.6.2 Read and write I / O memory resources
Almost every peripheral is made by a register on the reading device. Peripheral registers are also known as "I / O port", typically include: three major categories: control registers, status registers, and data registers, and one peripheral register is typically addressed. There are two ways to address the external IO port physical address: one is an I / O mapping method (I / O-mapped), and the other is a memory mapping method. Which of the specific use depends on the architecture of the CPU.
Some architectures of CPUs (eg, PowerPC, M68K, etc.) typically only implement a physical address space (RAM). In this case, the physical address of the peripheral I / O port is mapped to a single physical address space of the CPU. And becoming part of memory. At this point, the CPU can access the external I / O port as accessing a memory cell without providing a special peripheral I / O command. This is the so-called "memory mapping".
Other CPUs (typically x86) of some architectures (typically x86) are specially implemented a separate address space called "I / O address space" or "I / O port space". This is a different address space different from the physical address space of the CPU, all peripheral I / O ports are addressed in this space. The CPU accesses the address unit (i.e., I / O port) in this space by providing a special I / O instruction, such as the X86 IN and OUT instructions. This is the so-called "I / O Mapping Method" (I / O-Mapped). Compared to the RAM physical address space, I / O address space is usually smaller, such as the X86 CPU I / O space is only 64KB (0-0xfffff). This is a major disadvantage of "I / O Mapping Method".
Linux will be referred to as I / O port based on I / O mapping or memory mapping mode as "I / O region" (I / O Region). Before discussing the management of the I / O area, we first analyze how Linux implements the abstract concept of "I / O Resources". 3.1 Description of Linux to I / O resources
Linux designs a generic data structure resource to describe a variety of I / O resources (such as: I / O port, peripheral memory, DMA, and IRQ, etc.). This structure is defined in the include / linux / Ioport.h header file:
Struct resource {const char * name; unsigned long start, end; unsigned long flags; structure resource * parent, * sibling, * child;}
The meaning of each member is as follows:
1. Name pointer: Points the name of this resource. 2. Start and end: Represent the starting physical address of the resource and termination of the physical address. They determine the scope of resources, that is, a closed interval [start, end]. 3. Flags: Describe the flag of this resource attribute (see below). 4. Pointer Parent, Sibling and Child: The pointers pointing to father, brothers and child resources, respectively.
Properties Flags are a 32-bit flag value of a unsigned long-type to describe the properties of the resource. For example: the type of resource, whether it is read-only, whether it can be cached, and if it has been taken. Below is a part of the commonly used property flag (IOPORT.H):
/ ** IO Resources Have these Defined flags. * / # Define ioresource_bits 0x000000FF / * Bus-Specific Bits * /
#define ioresource_io 0x00000100 / * Resource Type * / # Define iorellce_mem 0x00000200 # Define ioreSource_irq 0x00000400 # Define ioreesource_dma 0x00000800
#define IORESOURCE_PREFETCH 0x00001000 / * No side effects * / # define IORESOURCE_READONLY 0x00002000 # define IORESOURCE_CACHEABLE 0x00004000 # define IORESOURCE_RANGELENGTH 0x00008000 # define IORESOURCE_SHADOWABLE 0x00010000 # define IORESOURCE_BUS_HAS_VGA 0x00080000
#define ioresource_unset 0x20000000 # Define iorellce_AUTO 0X40000000 # Define ioreesource_busy 0x80000000 / * Driver Has Marked this Resource Busy * /
The setting of the pointer Parent, Sibling and Child is to manage various I / O resources in the form of a tree.
3.2 Linux management of I / O resources
Linux is an inverted tree structure to manage each class I / O resource (such as I / O port, peripheral memory, DMA, and IRQ). Each type of I / O resource corresponds to an inverted resource tree. Each node in the tree is a resource structure, and the root node root of the tree describes the entire resource space of such resources. Based on the above idea, Linux implements the application, release, and lookup of resources in the kernel / resource.c file.
3.2.1 Application for I / O Resources
Suppose a certain type of resources have such a resource tree:
Nodes ROOT, R1, R2 and R3 are actually a resource type. Subsource R1, R2 and R3 are linked into a one-way non-circular chain table by the Sibling pointer, and the header is defined by the Child pointer in the root node, and therefore also known as the sub-resource linked list of parent resources. The PARENT pointers of R1, R2 and R3 points to their parent resource nodes, which are also the root node in the figure.
It is assumed that a segment I / O resource is assigned in the root node (represented by the shaded area in the figure). Function Request_Resource () implements this feature. It has two parameters: 1Root pointer, indicating which resource node is assigned; 2NEW pointer, pointing to the Resource structure of the resource to be assigned (ie, the shaded area in the figure). The source code of this function is as follows (kernel / resource.c):
INT Request_Resource (Struct Resource * root, struct resource * new) {struct resource * conflict;
Write_lock (& resource_lock); conflict = __REQUEST_RESOURCE (root, new); Write_Unlock; Return Conflict? -ebusy: 0;}
Note to the above functions is as follows:
1 Resource Lock Resource_lock reads and writes all resource trees, and any code segment must hold the lock before accessing a resource tree. It is defined as follows (kernel / resource.c):
Static rwlock_t resource_lock = rw_lock_unlocked;
2 It can be seen that the function is actually the actual resource allocation work by calling internal static functions __request_resource (). If the function returns a non-empty pointer, it means a resource conflict; otherwise, returning null means the assignment is successful.
3 Finally, if the Conflict pointer is null, the request_resource () function returns the return value 0, indicates success; otherwise, returns -ebusy means that the resource you want to assign is already occupied.
Function __request_resource () completes the actual resource allocation. If part of the resource described by the parameter NEW has been occupied by other nodes, the function returns a pointer to the Resource structure conflicting with the New. Otherwise, returns NULL. The source code of this function is as follows
(Kernel / Resource.c): / * Return the conflict entry if you can not request it * / static struct resource * __request_resource (struct resource * root, struct resource * new) {unsigned long start = new-> start; unsigned Long end = new-> end; struct resource * TMP, ** P; if (end Note for functions: 1 The first three IF statements determine if the resource scope described in NEW is included in the root, and whether it is a valid resource (because the End must be greater than START). Otherwise, return the root pointer, indicating conflicts with the root node. 2 Next, use a for loop over the root of root of the root node to check if there is a resource conflict, and insert the new NEW into the child's appropriate location (Child Link) from the low to high to high Arranged). To this end, it points to the RESOURCE structure that is currently being scanned with the TMP pointer, with a pointer P pointing forward a resource structure of the Sibling pointer member variable, and the initial value of the P Root-> Sibling. The implementation steps of the for cycler are as follows: l Let TMP point to the Resource structure (TMP = * P) currently being scanned. l Judging whether the TMP pointer is empty (TMP pointer is empty), or it has traversed the entire Child Link list), or the starting position of the currently scan node is larger than the end position of the NEW. As long as one of these two conditions is established, there is no resource conflict, so you can link the New link into the Child Link: 1 Set the new Sibling pointer to the node TMP (new-> sibling = TMP) that is currently being scanned (new-> sibling = TMP); 2 The SIBLING pointer of the front brothers node of the current node TMP is modified to point to the NEW node (* p = new); 3 Setting the New's PARENT pointer to point to root. The function can then return (return value NULL means no resource conflict). l If the above two conditions are not established, this shows that the resource domain currently scanned node may conflict with the New (actually two closed intervals), so it is necessary to further judge. To this end, it first modifies the pointer P, let it point to TMP-> Sibling to continue to scan the Child Link list. Then, if the TMP-> End is less than New-> start, if less than, the current node TMP and NEW do not have a resource conflict, so execute the Continue statement, continue to scan the Child Lin table down. Otherwise, if TMP-> End is greater than or equal to New-> Start, there is an intersection between Tmp -> [Start, End] and New -> [Start, End]. So return to the pointer TMP of the current node, indicating a resource conflict. 3.2.2 Release of Resources Function Release_Resource () is used to implement release of I / O resources. This function has only one parameter - that is, the pointer OLD, which points to the resources you want to release. The origin code is as follows: INT Release_Resource (struct resource * old) {int RetVal Write_lock (& resource_lock); retval = __release_resource (old); Write_Unlock; Return RetVal; It can be seen that it actually completes the actual resource release work by calling the __release_resource () internal static function. The main task of the function __release_resource () is to remove the resource area OLD (if already existing) from its parent resource's Child Link list, its source code is as follows: Static int __release_resource (struct resource * old) {struct resource * tmp, ** P; P = & old-> parent-> child; for (;;) {tmp = * p; if (! tmp) Break; if (tmp == OLD) {* p = tmp-> sibling; old-> parent = null Return 0;} P = & tmp-> sibling;} return-device;} Note to the above function code as follows: Similar to the function __request_resource (), the function is also through a FOR loop to traverse the child's Child Link list. To do this, it allows the TMP pointer to point to the current scanned resources, and pointer P pointing to the SIBLING member of the previous node of the current node (the initial value of P pointing to the Child pointer to the parent resource). The steps of the cyclic body are as follows: 1 First, let the TMP pointer point to the currently scanned node (TMP = * P). 2 If the TMP pointer is empty, the full Child Link list has been traversed, so execute the BREAK statement to introduce the for loop. Since the resource node specified in the CHILD list is not found during the traversal, the error value is finally returned to EINVAL, indicating that the parameter OLD is an invalid value. 3 Next, it is judge whether or not the current scan node is the resource node specified by the parameter OLD. If so, remove the OLD from the Child Link list, that is, let the Sibling pointer of the previous brothers node of the current node TMP point to the next node of TMP, and then set the OLD-> Parent pointer to NULL. Finally, the 0 value indicates that the execution is successful. 4 If the current scan node is not a resource OLD, continue to scan the next element in the Child Link. Therefore, the pointer P point to the TMP-> Sibling member. 3.2.3 Check if the resource has been occupied, Function Check_Resource () is used to implement if a section I / O resource has been occupied. The source code is as follows: Int Check_Resource (Struct Resource * root, unsigned long start, unsigned long list {structure resource * conflict, tmp tmp.start = start; tmp.end = start len - 1; write_lock (& resource_lock); conflict = __request_resource (root, & tmp); if (conflict!) __release_resource (& tmp); write_unlock (& resource_lock); return conflict -EBUSY? : 0; Note to this function is as follows: 1 Construct a temporary resource TMP, indicating the resources you want to check [START, START END-1]. 2 Call the __request_resource () function to apply for the resource represented by TMP in the root node root. If the resources described by TMP are also used, the function returns NULL, otherwise the non-empty pointer is returned. Therefore, in the case of conflict as NULL, call __release_resource () will release the resource you just applied. 3 Finally, depending on whether conflict is NULL, returns -ebusy or 0 value. 3.2.4 Find available resources Functions Find_Resource () are used to find unused and satisfying a given condition in a resource tree (that is, the resource length is Size, and within the [Min, Max] interval). The source code of its function is as follows: / ** Find empty slot in the resource tree given range and alignment. * / Static int find_resource (struct resource * root, struct resource * new, unsigned long size, unsigned long min, unsigned long max, unsigned long align, void (* Alignf) (void *, struct resource *, unsigned long), void * alignf_data) {struct resource * this = root-> child; New-> start = root-> start; for (;;) {if (this) new-> end = this-> start; else new-> end = root-> end; if (new-> start Similarly, the function also traverses the root of the Child Link list to find the unused resource void. To do this, it makes the THIS pointer indicates the sub-resource node that is currently being scanned. The initial value is equal to root-> child, which is to point to the first node in the child list, and let the new-> start initial value equal to root-> START, then start scanning the Child Link with a FOR cycle, for each scanned node, the cyclic body performs as follows: 1 First, determine if the THIS pointer is NULL. If not empty, let NEW-> End is equal to this-> start, that is, let resource NEW represents the unused resource interval in front of the current resource node THIS. 2 If the THIS pointer is empty, let the new-> End equal to root-> end. This has two layers: the first case is the root node's Child pointer to null (ie, the root node does not have any sub-resources). So this time is temporarily placed in the maximum. The second case is that it has traveled over the entire Child Link list, so let NEW indicate the unused resource interval behind the last sub-resource. 3 Fixed the value of New -> [Start, End] based on parameters min and Max to enable resource NEWs in the [Min, Max] area. 4 Next, alignment operation. 5 Then, it is determined whether or not the resource area NEW formed by the above steps is a valid resource (End must be greater than or equal to START), and the length of the resource area satisfies the requirements of the SIZE parameter (End-start 1> = size). If these two conditions are met, then we have found a voids that meet the conditions. Therefore, after fixing the value of the new-> End, then it can be returned (the return value 0 is successful). 6 If the above two conditions cannot be satisfied, the description has not been found, so we must continue the scanning list. Before we continue to scan, we still have to judge whether the THIS pointer is empty. If it is empty, it means that the full Child Link list has been scanned, so you can launch the for loop. Otherwise, you will modify the value of new-> start to this-> end 1, and let this point to the next brother resource node to continue the next sub-resource node in the chain list. 3.2.5 Distribution Interface Allocate_Resource () On the Find_Resource () function, the function allocate_resource () implements: assigns a specified size in a resource tree, and is included in the specified area [min, max], unused resource area. The source code is as follows: / ** Allocate empty slot in the resource tree given range and alignment. * / Int allocate_resource (struct resource * root, struct resource * new, unsigned long size, unsigned long min, unsigned long max, unsigned long align, void (* alignf (void *, struct resource *, unsigned long), void * alignf_data) {int err; write_lock (& resource_lock); err = find_resource (root, new, size, min, max, align, alignf, alignf_data); if (err> = 0 && __request_resource (root, new)) err = -EBUSY; write_unlock (& resource_lock); Return Err; 3.2.6 Getting Resources Name List Function get_resource_list () is used to get a list of sub-resource names for root node root. This function is primarily used to support / proc / file systems (such as implementing Proc / IOPORTS files and / proc / omem files). The source code is as follows: INT GET_RESOURCE_LIST (Struct Resource * root, char * buf, int size) {char * fmt; int RetVal FMT = "% 08LX-% 08LX:% S"; if (root-> end <0x10000) FMT = "% 04LX-% 04LX:% S"; read_lock (& resource_lock); retval = do_resource_list (root-> child, fmt , 8, buf, buf size) -buf; read_unlock (& resource_lock); return rettval;} It can be seen that the function is mainly implemented by calling internal static functions do_resource_list (), and its source code is as follows: / ** this generates report for / proc / iports and / proc / omem * / static char * do_resource_list (struct resource * entry, const char * fmt, int offset, char * buf, char * end) {if (Offset <0 ) OFFSET = 0; While (entry) {const char * name = entry-> name; unsigned long from, TO; IF ((int) (end-buf) <80) Return BUF; From = entry-> start; to = entry-> end; if (! Name) Name = "" BUF = Sprintf (buf, fmt offset, from, to, name); if (entry-> child) buf = do_resource_list (entry-> Child, FMT, Offset-2, BUF, End); entry = entry-> Sibling; Return BUF; The function do_resource_list () is mainly implemented by a while {} loop and recursive nested calls, which is simpler, which is not explained in detail here. 3.3 Manage I / O Region resources Linux will be collectively referred to as I / O port resources based on I / O mapping methods and I / O port resources based on memory maps (I / O Region). I / O Region is still an I / O resource, so it can still be described by the resource structure type. Let's take a look at how Linux manages I / O Region. 3.3.1 I / O Region Assignment On the basis of function __request_resource (), Linux implements function __Request_region () for assigning I / O regions, as follows: 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 = ioressource_busy; Write_lock (& resource_lock); For (;;) {struct resource * conflict; Conflict = __REQUEST_RESOURCE (PARENT, RES); if (! conflict) Break; if (conflict! = parent) {parent = conflict; if (! (! (")) Continue;} / * Uhhuh, there.. * / Kfree (res); res = null; break;} Write_unlock;} return res;} Note: 1 First, call the kmalloc () function allocated a resource structure in the SLAB distributor cache. 2 Then, the corresponding RESOURCE structure is initialized according to the parameter value. note! Flags members are initialized to iRessource_busy. 3 Next, the resource allocation is started with a FOR cycle, and the steps of the cyclic body are as follows: l First, call the __request_resource () function for resource allocation. If you return null, the assignment is successful, so the BREAK statement is executed to introduce the FOR loop, returns the pointer of the assigned resource structure, and the function successfully ends. l If the __request_resource () function assignment is unsuccessful, it is further determined whether the returned conflict resource node is the parent resource node Parent. If not, the assignment behavior drops a level, that is, try to assign in the currently conflict resource node (only if the resource node in conflict is available), so that the Parent pointer is equal to Conflict, and in Conflict- > FLAGS & iRESOURCE_BUSY is 0, execute the Continue statement to continue the FOR loop. l Otherwise, if the conflict resource node is the parent node Parent, or the conflict resource node sets the iResource_busy flag, the assignment failed. Then call the Kfree () function release the assigned resource structure and places the RES pointer to NULL, and finally introduced the For loop with the BREAK statement. 4 Finally, return the pointer of the allocated resource structure. 3.3.2 I / O Region release Function __release_region () Implementation I / O Region in a parent resource node Parent. In fact, the implementation of this function is similar to __release_resource (). The source code is as follows: Void __release_region (strunt resource * parent, unsigned long n) {structure 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 (! (r-> flagsource_busy)) {p = & res-> child; continue;} (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); Similarly, the function is also through a FOR loop to traverse the parent resource Parent's Child Link list. To do this, it allows the pointer RES to point to the sub-resource node currently being scanned, the pointer P refers to the SIBLING member variable of the forward a sub-resource node, and the initial value of P is pointing to Parent-> Child. The steps of the FOR cycle are as follows: 1 Let the RES pointer points to the currently scanned sub-resource node (res = * P). 2 If the RES pointer is NULL, the full Child Link list has been scanned, so exiting the for loop. 3 If the RES pointer is not null, continue to see if the specified I / O area is completely included in the current resource node, so that [START, START N-1] is included in RES -> [Start , END]. If not, let P point to the SIBLING member of the current resource node and continue the for loop. If it belongs, the following steps are performed: l First look at whether the current resource node sets the ioressource_busy flag. If this flag is not set, then the resource node may have a child node, so the scanning process drops a level, so modifying the P pointer, allowing it to point to RES-> Child, then execute the Continue statement to continue the for loop. l If the iResource_busy flag is set. It must be ensured that the current resource node is the specified I / O area, and then removes the current resource node from its parent resource's Child Link list. This can be implemented by letting the Sibling pointer of the previous brother resource node point to the next brother resource node of the current resource node, and finally call the KFree () function to release the Resource structure of the current resource node. The function can then be successfully returned. 3.3.3 Check if the specified I / O Region has been occupied Function __check_region () Check if the specified I / O Region is occupied. The source code is as follows: INT __CHECK_REGON (STRUCT RESOURCE * PARENT, 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;} The implementation of this function is similar to the implementation of __check_resource (). First, it tries to assign the specified I / O Region in the parent resource Parent by calling the __request_region () function. If the assignment is unsuccessful, NULL will be returned, so the function returns the error value-Ebusy indicates that the specified I / O Region is occupied. If the RES pointer is not empty, the specified I / O Region is not occupied. Then call the __release_resource () function releases the resource allocated (actually removing the RES structure from the child's Child Log), then call the Kfree () function to release the memory occupied by the RES structure. Finally, return 0 value indicates that the specified I / O Region is not occupied. 3.4 Manage I / O Port Resources We all know that the X86 processor using I / O mapping is implemented for peripherals, which is "I / O Space" (I / O Space) or "I / O Port Space", Its size is 64kb (0x0000-0xfffffff). LINUX implements the concept of "I / O Port Space" on all platforms supported. Since the I / O space is very small, even if the peripheral bus has a separate I / O port space, it is not all peripherals to map its I / O port (referring to the register) to "I / O port space" in. For example, most PCI cards are mapped to the CPU's RAM physical address space through the memory mapping mode. Old-style ISA cards often map their I / O ports into the I / O port space. Linux is based on the concept of "I / O Region" to implement the I / O port resource (I / O-MAPPED or MEMORY-MAPPED). 3.4.1 Definition of Resource Root Node Linux defines global variables IOPORT_RESOURCE and IOMEM_RESOURCE in the kernel / resource.c file to describe the entire I / O port space based on I / O mapping mode and the I / O memory resource space based on memory mapping mode (including I / O). Port and peripheral memory). It is defined as follows: Struct Resource IOPORT_RESOURCE = {"PCI IO", 0x0000, IO_SPACE_LIMIT, IORESOURCE_IO}; struct resource omem_resource = {"PCI MEM", 0x00000000, 0xffffff, ioressource_mem}; Among them, macro IO_SPACE_LIMIT represents the size of the entire I / O space, for the X86 platform, it is 0xffff (defined in the include / ASM-I386 / IO.h header). Obviously, the size of the I / O memory space is 4GB. 3.4.2 Operation of I / O port space Based on I / O Region operation function __xxx_region (), Linux defines three macros that operate on I / O port space in header file include / linux / iport.h: 1Request_region () macro, request in I / O The I / O port resources of the specified range are allocated in the port space. 2Check_Region () macro, check if the specified I / O port resource in the I / O port space has been occupied. 3RELEASE_REGON () macro, release the specified I / O port resource in the I / O port space. The definition of these three macros is as follows: #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_REGON (& IOOPORT_RESOURCE, (START), (N)) Where the macro parameter start specifies the starting physical address of the I / O port resource (which is the physical address in the I / O port space), the macro parameter n Specifies the size of the I / O port resource. 3.4.3 Operation of I / O Memory Resources Based on I / O Region operation function __xxx_region (), Linux defines three macros that operate on I / O memory resources in header file include / linux / Ioport.h: 1Request_Mem_Region () macro, request assignment I / O memory resources. 2CHECK_ MEM_REGON () macro, check if the specified I / O memory resource has been occupied. 3RELEASE_ MEM_REGON () macro, release the specified I / O memory resources. These three macros are defined as follows: #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)) The parameter start is the starting physical address of the I / O memory resource (which is the physical address in the RAM physical address space of the CPU), and the parameter n specifies the size of the I / O memory resource. 3.4.4 Support for / Proc / Ioports and / Proc / Iomem Linux defines two macros in IOPORT.H header: Get_ioport_list () and get_iomem_list (), are used to implement the / proc / omem file, respectively. It is defined as follows: #define get_ioport_list (buf) get_resource_list (& IOOPORT_RESOURCE, BUF, PAGE_SIZE) #define get_mem_list (buf) get_resource_list (& IOMEM_RESOURCE, BUF, PAGE_SIZE) 3.5 Access I / O Port Space After the driver requests port resources in the I / O port space, it can read and write these I / O ports via the CPU IO. One thing to note when reading and writing I / O ports is that most platforms distinguish between 8 bits, 16-bit, and 32-bit ports, that is, pay attention to the width of the I / O port. Linux defines a series of macro functions that read and write different width I / O ports in include / ASM-I386 / IO.H for i386 platforms. As follows: (1) Read and write 8-bit wide I / O port Unsigned char inb (unsigned port); Void Outb (unsigned port); Where the port parameter specifies the port address in the I / O port space. On most platforms (such as x86) It is a UNSIGNED SHORT type, and on some of the other platforms is a unsigned int type. Obviously, the type of port address is determined by the size of the I / O port space. (2) Read and write 16-bit wide I / O port UNSIGNED PORT INW; Void Outw (Unsigned "; (3) Read and write 32-bit wide I / O ports Unsigned int inl (unsigned port); void outl (unsigned int value, unsigned port); 3.5.1 String operation on I / O port In addition to the above "single-shot" I / O operation, some CPUs also support a continuous read and write operation for an I / O port, which is read or written for a single I / O port. Series bytes, words or 32-bit integers, this is called "string I / O command". This instruction is obviously much better than the same functionality than the cycle. Linux also defines a string I / O read and write function in the IO.H file: (1) 8-bit wide string I / O operation Void INSB (UNSIGNED Port, Void * Addr, Unsigned long count); Void Outsb (unsigned port, void * addr, unsigned long count); (2 )16-bit string I / O operation Void INSW (UNSIGNED Port, Void * Addr, Unsigned long count); Void Outsw (unsigned port, void * addr, unsigned long count); (3) 32-bit width string I / O operation Void INSL (UNSIGNED Port, Void * Addr, Unsigned Long Count); Void Outsl (unsigned port, void * addr, unsigned long count); 3.5.2 Pausing I / O On some platforms (typically x86), for the slow peripherals on the old-fashioned bus (such as ISA), if the CPU reads and wrote its I / O port is too fast, the phenomenon of loss of data may occur. . The solution to this problem is to insert a small delay between two consecutive I / O operations to wait for a slow space. This is the so-called "pausing I / O". For PAUSING I / O, Linux also defines its I / O readwrities in the IO.h header file, and is named XXX_P, such as inb_p (), Outb_P (), and more. Below we are analyzed as examples. The macro definition __out (b, "B" char) in IO.H can be defined as follows: Extern inline void outb (unsigned short port) {__ASM__ __Volatile__ ("OUTB%" "B" "0,%" W "" 1 ":" a "(Value)," ND "(Port) } Extern inline void outb_p (unsigned short port) {__ASM____volatile__ ("OUTB%" "B" "0,%" "W" "1" __full_slow_down_io:: "a" (Value), "ND" (Port " ))))));} It can be seen that the implementation of the OUTB_P () function is inserted into the macro __full_slown_down_io to achieve minute delay. Macro __full_slown_down_io is defined at the beginning of the header file IO.H: #ifdef slow_io_by_jumping # define __slow_down_io "JMP 1F1: JMP 1F1:" # 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 Obviously, __ full_slow_down_io is one or four __slow_down_io (decide if the macro real_slow_io is defined), and the macro __slow_down_io is defined as a meaningless jump statement or the write port 0x80 operation (depending on whether it defines the macro SLOW_IO_BY_JUMPING Decide). 3.6 Access I / O Memory Resources Although I / O port space has been widely used on the X86 platform, because it is very small, most modern bus devices map its I / O port in memory mapping (I / O). / O register) and peripheral memory. The I / O port based on memory mapping (I / O register) and peripheral memory can be known to "I / O Memory" resource (I / O Memory). Because the differences in hardware implementation are completely transparent for software, the driver developers can regard the I / O ports of the memory mapping method and peripheral memory uniform as "I / O Memory" resource. From the preceding section, we know that I / O memory resources are derived within a single memory physical address space of the CPU, that is, it and the system RAM are in a physical address space. Therefore, I / O memory resources can be accessed through the interview instruction of the CPU. In general, the physical address of the I / O memory resource for peripherals is known when the system is running, which can be assigned by the system firmware (such as the BIOS), or the hardwire of the device (HardWired) get. For example, the physical address of the PCI card I / O memory resource is assigned by the PCI BIOS when the system is started and written to the BAR in the configuration space of the PCI card. The physical address of the I / O memory resource of the ISA card is mapped to 640KB-1MB by the device hard wiring. However, the CPU usually does not predefine the virtual address range for these known peripheral I / O memory resources, as they are known after the system is started (there is a dynamic in a sense), so drive The program cannot access I / O memory resources directly through the physical address, and they must map them into the core virtual address space (by page table), and then access these i by visiting instructions based on the core virtual address range obtained by the mapping. / O memory resources. 3.6.1 Mapping I / O Memory Resources Linux declares the function ioremap () in the IO.h header file, is used to map the physical address of the I / O memory resource to the core virtual address space (3GB-4GB), as follows: Void * ioremap (unsigned long pHys_addr, unsigned long size, unsigned long flags); Void IounMap (Void * AddR); The function is used to cancel the mapping made by ioremap (), and the parameter addr is a pointer to the core virtual address. These two functions are implemented in the mm / ioremap.c file. Specific implementation can be referred to the book "Scenario Analysis". 3.6.2 Read and write I / O memory resources After mapping the physical address of the I / O memory resource into the core virtual address, we can directly read and write I / O memory resources like reading and writing RAM. However, due to different access processing on I / O memory and system memory, in order to ensure cross-platform compatibility, Linux implements a function of a series of read and write I / O memory resources, these functions Different platforms have different implementations. However, on the X86 platform, the read and write I / O memory is not different from the read and write RAM. As shown below (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 (_1_virt (a), (b), (c)) # define memcpy_fromio (a, b, c) Memcpy ((a), __ io_virt (b), (c)) #define memcpy_toio (a, b, c) Memcpy (_1_virt (a), (b))) The above definitions The macro __IO_VIRT () only checks if the deficiency address ADDR is the virtual address in the core space. The implementation of the macro in core 2.4.0 is temporary. The specific implementation function is in the ARCH / I386 / lib / odebug.c file. Obviously, accessing I / O memory resources on the X86 platform is not different from accessing system main memory RAM. However, in order to ensure the cross-platform portability of the driver, we should use the above functions to access the I / O memory resources, and should not be accessed by pointers to the core virtual address.