Management of I/O Ports and I/O memory in Linux
Port is the address of the register that can be directly accessed by the CPU in the interface circuit. Almost every type of peripherals is performed by reading and writing registers on the device. The CPU sends commands to registers in the interface circuit through these addresses to read the status and transmit data. A peripheral register, also known as an "I/O port", usually includes three categories: control registers, status registers, and data registers, and a peripheral register is usually continuously edited.
Ii. Io memory
For example, a graphic card can be inserted on a PC with a 2 MB Storage space or even a ROM containing executable code.
Iii. IO port and IO memory differentiation and Connection
The distinction between the two involves hardware knowledge. In the x86 system, there are two address spaces: I/O space and memory space, while the CPU (such as arm and PowerPC) of the Proteus Command System) generally, only one physical address space is implemented, that is, the memory space.
Memory space:Memory Address addressing range. The 32-bit operating system memory space is a power of 32, that is, 4 GB.
Io space:X86 has a unique space, which is independent from the memory space. 32-bit x86 has 64 k I/O space.
IO port:When the register or memory is in the I/O space, it is called the I/O port. General registers are also known as I/O ports, or I/O ports. This I/O port can be mapped to memory space or I/O space.
Io memory: When the register or memory is in the memory space, it is called Io memory.
Iv. Addressing of physical IP addresses of peripheral I/O Ports
The CPU can address the physical address of the peripheral I/O port in two ways: I/O ing (I/O-mapped ), another method is memory-mapped ). The specific method depends on the CPU architecture.
1. Unified addressing
Generally, the CPU (such as PowerPC, m68k, and arm) of a CPU of a server-defined identity management system only implements one 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 becomes a part of the memory. In this case, the CPU can access the peripheral I/O port as it accesses a memory unit, without the need to set up a dedicated peripheral I/O command.
The unified addressing is also called the "I/O memory" method. The peripheral registers are located in the "memory space" (many peripherals have their own memory and buffer zone, the registers and memory of peripherals are collectively referred to as "I/O space ").
2. Independent addressing
In addition, some architecture CPUs (typically x86) provide a dedicated address space for peripherals, it is called "I/O address space" or "I/O port space ". This is an address space different from the physical address space of the CPU. All the peripheral I/O ports are located in this space. The CPU uses dedicated I/O commands (such as x86 In and out commands) to access the address units (I/O Ports) in the space ). Compared with the ram physical address space, I/O address space is usually relatively small, for example, x86 cpu I/O space is only 64 KB (0-0xffff ). This is a major drawback of the "I/O ing method.
The independent address is also called the "I/O port" mode, and the peripheral registers are located in the "I/O (Address) Space ".
3. Advantages and Disadvantages
The main advantages of independent addressing are:
1) the I/O port address does not occupy memory space. It uses dedicated I/O commands to operate on the port. The I/O commands are short and fast.
2) The special I/O commands are significantly different from the memory access commands, so that the I/O operations and memory operations in the program are clear and the program is readable.
3) at the same time, because a dedicated I/O command is used to access the port, and the I/O port address and the memory address are separated, therefore, the I/O port address and the memory address can overlap without confusion.
4). The decoding circuit is relatively simple (because the address space of port I/0 is generally small, and the address lines used are also small ).
The disadvantage is that only dedicated I/0 commands can be used. There are more methods to access the memory than to access the port.
Advantages of unified addressing:
1) because the access to the I/O device uses commands for access to the memory, there are many command types and complete functions, this not only enables access to the I/O port to implement input/output operations, but also performs arithmetic logic operations and shift operations on the port content;
2) In addition, the port can have a large address space, which makes sense for large control systems and data communication systems.
The disadvantage of this method is that the port occupies the address space of the memory and reduces the memory capacity. In addition, the instruction length is longer than that of the dedicated I/O command, so the execution speed is slow.
Which of the following depends on the overall system design. Two methods can be used in a system, provided that I/O independent addressing is supported first. Intel x86 microprocessor supports independent I/O addressing, because their instruction systems contain I/O commands, and set control signal pins that can distinguish I/O access from memory access. Some microprocessor or single-chip microcomputer, in order to reduce the pin and reduce the occupied area of the chip, do not support independent I/O addressing, and can only adopt unified memory addressing.
V. Access the I/O port in Linux
For an established system, it is either an independent address or a unified address. The specific method depends on the CPU architecture. For example, PowerPC and m68k adopt uniform addressing, while x86 adopt independent addressing, which has the concept of Io space. Currently, most embedded microcontroller such as arm and PowerPC do not provide I/O space. They only have memory space and can be accessed directly using addresses and pointers. But for Linux kernel, it may be used for different CPUs, so it must consider both methods, so it adopts a new method, i/O port based on I/O ing or memory ing is called "I/O Region" (I/O
Region), no matter which method you use, you must first apply for the IO region: request_resource () and release it at the end: release_resource ().
Io region is an IO resource, so it can be described by the resource structure type.
There are two ways to access the IO port: I/O ing (I/O-mapped) and memory ing (memory-mapped ). In the previous approach, functions such as intb ()/outb () are directly used to read and write Io ports without being mapped to the memory space; the latter type of mmio maps the I/O port to the I/O memory ("memory space"), and then uses the function to access the I/O memory to access the I/O port.
1. I/O ing
Directly use the I/O port operation function: when the device is enabled or the driver module is loaded, apply for the I/O port area, and then use INB (), outb () and so on for port access, finally, release the IO port range when the device is off or the driver is detached.
In, out, INS, and outs assembly language commands can all access the I/O port. The kernel contains the following helper functions to simplify this access:
INB (), inw (), INL ()
Read 1, 2, or 4 consecutive bytes from the I/O Ports respectively. The suffixes "B", "W", and "L" represent one byte (8 bits), one word (16 bits), and one long integer (32 bits ).
Inb_p (), inw_p (), inl_p ()
Read 1, 2, or 4 consecutive bytes from the I/O Ports respectively, and execute a "dummy (empty command)" command to suspend the CPU.
Outb (), outw (), outl ()
Write 1, 2, or 4 consecutive bytes to an I/O port respectively.
Outb_p (), outw_p (), outl_p ()
Write 1, 2, or 4 consecutive bytes to an I/O port respectively, and then execute a "Dummy" command to suspend the CPU.
InSb (), insw (), insl ()
Read sequential bytes in a group of 1, 2, or 4 bytes from the I/O Ports. The length of the byte sequence is given by the parameter of this function.
Outsb (), outsw (), outsl ()
Write a continuous byte sequence consisting of 1, 2, or 4 bytes to the I/O port respectively.
The process is as follows:
Although it is very simple to access the I/O port, it may not be so easy to detect which I/O ports have been allocated to I/O devices, especially for Systems Based on the ISA bus. Generally, I/O device drivers need to write data to an I/O port blindly to detect hardware devices. However, if other hardware devices already use this port, then the system will crash. To prevent this, the kernel must use "Resources" to record the I/O Ports allocated to each hardware device. A resource indicates a part of an object, which is mutually exclusive to the device driver. Here, the resource represents a range of I/O port addresses. The information corresponding to each resource is stored in the resource data structure:
[Plain]
View plaincopyprint?
- Struct resource {
- Resource_size_t start; // start of the resource range
- Resource_size_t end; // end of the resource range
- Const char * Name; // name of the resource owner
- Unsigned long flags; // various flags
- Struct resource * parent, * sibling, * child; // pointer to the father, brother, and child in the Resource Tree
- };
Struct resource {resource_size_t start; // start of the resource range resource_size_t end; // end of the resource range const char * Name; // The Name Of The resource owner unsigned long flags; // various indicators struct resource * parent, * sibling, * child; // pointer to the father, brother, and child in the Resource Tree };
All resources of the same type are inserted into a tree data structure (father, brother, and child). For example, all resources in the I/O port address range are included in the tree where the root node is ioport_resource. The child of a node is collected in a linked list, and its first element is directed by child. The sibling field points to the next node in the linked list.
Why use a tree? For example, consider the I/O port address used by the IDE Hard Drive Interface-for example, from 0xf000 to 0xf00f. In this case, resources such as the start field 0xf000 and the end field 0xf00f are contained in the tree, and the general name of the controller is stored in the Name field. However, the driver of the IDE device needs to remember another information, that is, the sub-range from 0xf000 to 0xf007 used by the main disk of the IDE chain, from 0xf008 to 0xf00f. To achieve this, the device driver inserts the child with two subranges from 0xf000
To the resource corresponding to the entire range of 0xf00f. Generally, each node in the tree must be a sub-range of the parent node range. The root node of the I/O port Resource Tree (ioport_resource) spans the entire I/O address space (from Port 0 to 65535 ).
Any device driver can use the following three functions to pass their parameters to the root node of the Resource Tree and the address of the new resource data structure to be inserted:
Request_resource () // assigns a specified range to an I/O device.
Allocate_resource () // search for a available range of the given size and arrangement mode in the Resource Tree; if so, allocate this range to an I/O device (mainly used by the PCI device driver and can be configured using any port number and memory address on the motherboard ).
Release_resource () // the specified range previously allocated to the I/O device.
The kernel also defines some shortcut functions for the above functions that apply to the I/O port: request_region () allocates a specified range of I/O Ports, release_region () release the range previously allocated to the I/O port. The Tree of all I/O addresses currently allocated to the I/O device can be obtained from the/proc/ioports file.
2. Memory ing
Map the IO port to memory for access. when the device is on or the driver module is loaded, apply for the IO port region and use ioport_map () to map it To the memory, then use the IO memory function to access the port. Finally, release the IO port and release the ing when the device is disabled or the driver module is detached.
The ing function is prototype:
Void * ioport_map (unsigned long port, unsigned int count );
This function can be used to remap count consecutive I/O ports starting with port to a "memory space ". Then, you can access these I/O ports at the address they return, just like accessing the I/O memory. However, before ing, you must use request_region () to allocate the I/O port.
When you no longer need this ing, you need to call the following function to undo it:
Void ioport_unmap (void * ADDR );
After the physical address of the device is mapped to a virtual address, although you can directly access these addresses through pointers, you should use the following functions of the Linux kernel to access the I/O memory: · read I/O memory
Unsigned int ioread8 (void * ADDR );
Unsigned int ioread16 (void * ADDR );
Unsigned int ioread32 (void * ADDR );
Functions of earlier versions corresponding to the above functions are (these functions are still supported in Linux 2.6 ):
Unsigned readb (Address );
Unsigned readw (Address );
Unsigned readl (Address );
· Write I/O memory
Void iowrite8 (u8 value, void * ADDR );
Void iowrite16 (2010value, void * ADDR );
Void iowrite32 (u32 value, void * ADDR );
Functions of earlier versions corresponding to the above functions are (these functions are still supported in Linux 2.6 ):
Void writeb (unsigned value, address );
Void writew (unsigned value, address );
Void writel (unsigned value, address );
The process is as follows:
Vi. Access Io memory in Linux
I/O memory access method: First Call request_mem_region () to apply for resources, and then map the Register address to the virtual address of the kernel space through ioremap, then you can access these registers through the programming interface of the Linux device. After the access is complete, use ioremap () to release the applied virtual address and release the IO memory resources applied for by release_mem_region.
Struct resource * requset_mem_region (unsigned long start, unsigned long Len, char * Name );
This function applies for Len memory addresses from the kernel (in 3G ~ 4G virtual address), and here the start is the I/O physical address, name is the device name. Note ,. If the allocation is successful, non-null is returned. Otherwise, null is returned.
In addition, you can view the memory range provided by the system to various devices through/proc/iomem.
To release the applied I/O memory, use the release_mem_region () function:
Void release_mem_region (unsigned long start, unsigned long Len)
After applying for a set of I/O memory, call the ioremap () function:
Void * ioremap (unsigned long phys_addr, unsigned long size, unsigned long flags );
The meanings of the three parameters are as follows:
Phys_addr: the same I/O physical address as the start parameter in the requset_mem_region function;
Size: the size of the space to be mapped;
Flags: indicates the permission-related flag of the IO space to be mapped;
Function: maps an I/O address space to the virtual address space of the kernel (applied through release_mem_region)
The process is as follows:
6. ioremap and ioport_map
The following describes the source code of ioport_map and ioport_umap:
[Plain]
View plaincopyprint?
- Void _ iomem * ioport_map (unsigned long port, unsigned int nr)
- {
- If (Port> pio_mask)
- Return NULL;
- Return (void _ iomem *) (unsigned long) (Port + pio_offset );
- }
- Void ioport_unmap (void _ iomem * ADDR)
- {
- /* Nothing to do */
- }
void __iomem *ioport_map(unsigned long port, unsigned int nr){ if (port > PIO_MASK) return NULL; return (void __iomem *) (unsigned long) (port + PIO_OFFSET);}void ioport_unmap(void __iomem *addr){ /* Nothing to do */}
Ioport_map only adds pio_offset (64 K) to the port, while ioport_unmap does nothing. In this way, the 64 K space of portio is mapped to the 64 K ~ of the virtual address ~ K, while the virtual address returned by ioremap must be above 3G. The ioport_map function is designed to provide a virtual address space consistent with ioremap. After analyzing the source code of ioport_map (), we can find that the so-called ing to memory space is actually an illusion for developers and is not mapped to the kernel virtual address, only to allow engineers to use the unified I/O memory access interface ioread8/iowrite8 (......) access the I/O port.
Finally, let's take a look at the ioread8 Source Code. Its implementation is to determine the virtual address to distinguish the IO port and IO memory, and then use INB/outb and readb/writeb to read and write respectively.
[Plain]
View plaincopyprint?
- Unsigned int fastcall ioread8 (void _ iomem * ADDR)
- {
- Io_cond (ADDR, return INB (port), return readb (ADDR ));
- }
- # Define verify_pio (port) bug_on (Port &~ Pio_mask )! = Pio_offset)
- # Define io_cond (ADDR, is_pio, is_mmio) do {\
- Unsigned Long PORT = (unsigned long _ force) ADDR ;\
- If (Port <pio_reserved ){\
- Verify_pio (port );\
- Port & = pio_mask ;\
- Is_pio ;\
- } Else {\
- Is_mmio ;\
- }\
- } While (0)
- Expand:
- Unsigned int fastcall ioread8 (void _ iomem * ADDR)
- {
- Unsigned Long PORT = (unsigned long _ force) ADDR;
- If (Port <0x40000ul ){
- Bug_on (Port &~ Pio_mask )! = Pio_offset );
- Port & = pio_mask;
- Return INB (port );
- } Else {
- Return readb (ADDR );
- }
- }
Unsigned int fastcall ioread8 (void _ iomem * ADDR) {io_cond (ADDR, return INB (port), return readb (ADDR);} # define verify_pio (port) bug_on (Port &~ Pio_mask )! = Pio_offset) # define io_cond (ADDR, is_pio, is_mmio) do {\ unsigned Long PORT = (unsigned long _ force) ADDR; \ If (Port <pio_reserved) {\ verify_pio (port); \ Port & = pio_mask; \ is_pio ;\} else {\ is_mmio ;\}while (0) Expand: unsigned int fastcall ioread8 (void _ iomem * ADDR) {unsigned Long PORT = (unsigned long _ force) ADDR; If (Port <0x40000ul) {bug_on (Port &~ Pio_mask )! = Pio_offset); Port & = pio_mask; return INB (port) ;}else {return readb (ADDR );}}
VII. Summary
The IP address of the peripheral I/O register is an independent CPU address. In this case, the peripheral I/O register should be called an I/O port. The access I/O register can map it to the virtual address space through ioport_map, but in fact this is an "illusion" created for developers and is not mapped to the kernel virtual address, just to allow access to the IO registers using the same interface as the IO memory; you can also directly use the in/out command to access the IO register.
For example, the intel X86 platform uses a technology called memory o ing (mmio), which is part of the PCI specification. After the port of the IO Device is mapped to the memory space, the CPU accesses the I/O port just like the access memory.
The IP address of the peripheral I/O registers is the same as the CPU address. In this case, the peripheral I/O registers should be called the I/O memory. The I/O registers can be mapped to the virtual address space through ioremap, then use the read/write interface.