To ensure that the computer works properly, a data path must be provided to allow information to flow between the CPU, ram, and I/O devices connected to the PC. These data paths are called bus and act as the main communication channel in the computer.
All computers have a system bus that connects to most internal hardware devices. A typical system bus is the PCI (Peripheral Component Interconnect) bus. Currently, many other types of buses are used, such as ISA, Eisa, MCA, SCSI, and USB.
Typically, a computer includes several different types of buses that are connected by hardware devices called bridges. Two high-speed buses are used to transmit data back and forth on the memory chip: the front-end bus connects the CPU to the ram controller, while the back-end bus connects the CPU directly to the high-speed cache of external hardware. The bridge on the host connects the system bus with the front-end bus.
Any I/O device has and can connect only one bus. The type of the bus affects the internal design of the I/O device and how the kernel processes the device. This blog post will discuss the functional features shared by all PC architectures, without introducing the technical details of specific bus types.
The data path between the CPU and the I/O device is usually called the I/O bus. The 80x86 microprocessor uses a 16-bit address bus to address I/O devices, and uses an 8-bit, 16-bit, or 32-Bit Data Bus to transmit data. Each I/O device is connected to the I/O bus sequentially. This connection uses a hardware organization level that contains three elements: I/O port, interface, and device controller. Displays the components of the I/O architecture:
1 I/O port
Each device connected to the I/O bus has its own I/O address set, usually known as the I/O port (I/O port ). In the ibm pc architecture, the I/O address space provides a total of 65536 8-bit I/O Ports. Two consecutive eight-bit ports can be regarded as a 16-bit port, but this must start with an even number of addresses. Likewise, two consecutive 16-bit ports can also be considered as a 32-bit port, but this must start from an integer multiple of 4. There are four dedicated assembly language commands that allow the CPU to read and write the I/O Ports. They are in, INS, out, and outs. When executing a command, the CPU uses the address bus to select the requested I/O port, and uses the data bus to transmit data between the CPU register and port.
I/O ports can also be mapped to physical address spaces. Therefore, the communication between the processor and the I/O device can use assembly language commands (such as mov, And, or) that directly operate on the memory ). Modern hardware devices tend to map I/O, because the processing speed is faster and can be combined with DMA.
The main purpose of the system designer is to provide a uniform method for I/O programming without sacrificing performance. For this purpose, the I/O Ports of each device are organized into a set of dedicated registers, as shown in. The CPU writes the command to be sent to the device into the device control register and reads the value indicating the internal status of the device from the device status register. The CPU can also obtain data from the device by reading the content of the device input register, or by outputting the Register to the device) to output data to the device.
To reduce costs, the same I/O port is usually used for different purposes. For example, some bits describe the status of the device, while others specify the commands sent to the device. Similarly, the same I/O port can also be used as an input or output register.
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 a byte (8 bits), a word (16 bits), and a 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 the sequential bytes of 1, 2, or 4 bytes from the I/O port respectively. 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.
X86 does not use these auxiliary functions, because it is better to directly use in, out, INS, and outs. If you want to analyze other architectures such as arm and MPIS, you can check them out. We mainly analyze the x86 system, so we don't care about them.
Although it is very easy to access the I/O port, it may not be so easy to detect which I/O ports have been allocated to the I/O device. 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.
Resource indicates a part of an object, which is mutually exclusive to the device driver. In our case, a resource represents a range of I/O port addresses. The information corresponding to each resource is stored in the resource data structure. All resources of the same type are inserted into a tree data structure. For example, all resources that indicate the I/O port address range are included in the tree where the root node is ioport_resource:
Struct resource {
Resource_size_t start;/* Start of the resource range */
Resource_size_t end;/* end of the resource range */
Const char * Name;/* description of the resource owner */
Unsigned long flags;/* Various logos */
/* Pointer to the father, brother, and first child in the Resource Tree */
Struct resource * parent, * sibling, * child;
};
Struct resource ioport_resource = {
. Name = "PCI Io ",
. Start = 0,
. End = io_space_limit,
. Flags = ioresource_io,
};
# Define io_space_limit 0 xFFFF/* x86 system is a 16-bit bus port address */
Note that the first I/O port resource is assigned to the PCI interface. 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.
So why should we use trees instead of other forms such as linked lists? For example, consider the I/O port address used by the SCSI disk interface, for example, from 0xf000 to 0xf00f. Then, a resource such as the start field 0xf000 and the end field 0xf00f is included in the tree, and the general name of the controller is stored in the Name field. However, the SCSI device driver needs to remember another information, that is, the master disk of the SCSI chain uses 0xf000 ~ The sub-range of 0xf007, from the disk (slave disk) using 0xf008 ~ The sub-range of 0xf00f. To achieve this, the device driver inserts the child with two sub-ranges into 0xf00o ~ 0xf00f for the entire range of resources. 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 ~ 65535 ). A typical pc I/O port resource allocation is as follows:
0000 ~ 000f: DMA controller 1
0020 ~ 0021: Master Interrupt Controller
0040 ~ 0043: system clock
0060: keyboard controller control status Port
0061: System speaker
0064: keyboard controller data port
0070 ~ 0071: system CMOS/real-time clock
0080 ~ 0083: DMA controller 1
0087 ~ 0088: DMA controller 1
0089 ~ 008b: DMA controller 1
00a0 ~ 00a1: slave Interrupt Controller
00c0 ~ 00df: DMA controller 2
00f0 ~ 00FF: coprocessor
0170 ~ 0117: Standard IDE/ESDI Hard Drive Controller
01f0 ~ 01ff: Standard IDE/ESDI Hard Drive Controller
0200 ~ 0207: Game Port
0274 ~ 0277: ISA plug-and-play Counter
0278 ~ 027f: Parallel printer port
02f8 ~ 02ff: serial communication port 2 (com2)
0376: Second IDE hard disk Controller
0378 ~ 037f: Parallel print Port 1
03b0 ~ 03bb: VGA display adapter
03c0 ~ 03df: VGA display adapter
03d0 ~ 03df: Color Display Adapter
03f2 ~ 03f5: soft disk Controller
03f6: the first hard disk Controller
03f8 ~ 03ff: serial communication port 1 (COM1)
0400 ~ FFFF does not specify a port for user Extension
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.
Int request_resource (struct resource * root, struct resource * New)
{
Struct resource * Conflict;
Write_lock (& resource_lock );
Conflict = _ request_resource (root, new );
Write_unlock (& resource_lock );
Return conflict? -Ebusy: 0;
}
Static struct resource * _ request_resource (struct resource * root, struct resource * New)
{
Resource_size_t start = new-> start;
Resource_size_t end = new-> end;
Struct resource * TMP, ** P;
If (end <start)
Return root;
If (start <root-> start)
Return root;
If (end> root-> end)
Return root;
P = & root-> child;
For (;;){
TMP = * P;
If (! TMP | TMP-> Start> end ){
New-> sibling = TMP;
* P = new;
New-> parent = root;
Return NULL;
}
P = & TMP-> sibling;
If (TMP-> end <start)
Continue;
Return TMP;
}
}
Allocate_resource () -- search for a available range of the given size and Arrangement Method in the Resource Tree; if so, assign this range to an I/O device (mainly used by the PCI device driver, this type of driver can be configured to use any port number and the memory address on the motherboard ).
Int allocate_resource (struct resource * root, struct resource * New,
Resource_size_t size, resource_size_t min,
Resource_size_t Max, resource_size_t align,
Void (* alignf) (void *, struct resource *,
Resource_size_t, resource_size_t ),
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;
}
Static int find_resource (struct resource * root, struct resource * New,
Resource_size_t size, resource_size_t min,
Resource_size_t Max, resource_size_t align,
Void (* alignf) (void *, struct resource *,
Resource_size_t, resource_size_t ),
Void * alignf_data)
{
Struct resource * This = root-> child;
New-> Start = root-> start;
/*
* Skip past an allocated resource that starts at 0, since the assignment
* Of this-> Start-1 to new-> end below wowould cause an underflow.
*/
If (this & this-> Start = 0 ){
New-> Start = This-> end + 1;
This = This-> sibling;
}
For (;;){
If (this)
New-> end = This-> Start-1;
Else
New-> end = root-> end;
If (New-> Start <min)
New-> Start = min;
If (New-> end> MAX)
New-> end = max;
New-> Start = align (New-> Start, align );
If (alignf)
Alignf (alignf_data, new, size, align );
If (New-> Start <New-> end & New-> end-New-> Start> = size-1 ){
New-> end = new-> Start + size-1;
Return 0;
}
If (! This)
Break;
New-> Start = This-> end + 1;
This = This-> sibling;
}
Return-ebusy;
}
Release_resource () -- release the specified range previously allocated to the I/O device.
Int release_resource (struct resource * old)
{
Int retval;
Write_lock (& resource_lock );
Retval = _ release_resource (old );
Write_unlock (& resource_lock );
Return retval;
}
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-einval;
}
The kernel also defines some shortcut functions for the above functions applied 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. The following shows the final distribution of I/O Ports in my system environment:
[Root @ localhost proc] # Cat ioports
2017-001f: dma1
0020-0021: pic1
0040-0043: timer0
0050-0053: timer1
0060-0060: keyboard
0064-0064: keyboard
0070-0077: rtc
0080-008f: DMA page Reg
00a0-00a1: pic2
00c0-00df: dma2
00f0-00ff: FPU
02f8-02ff: Serial
03c0-03df: VGA +
03f8-03ff: Serial
0400-041f: 0000: 00: 1f. 3
0400-041f: i801_smbus
0800-0803: ACPI pm1a_evt_blk
0804-0805: ACPI pm1a_cnt_blk
0808-080b: ACPI pm_tmr
0810-0815: acpi cpu throttle
0820-082f: ACPI gpe0_blk
0850-0850: ACPI pm2_cnt_blk
0a00-0a0f: PNP
0ca2-0a3: PNP 00: 0c
0ca2-0ca: ipmi_si
0ca3-0a3: ipmi_si
A400-a41f: 0000: 00: 1d. 0
A400-a41f: uhci_hcd.
A480-a49f: 0000: 00: 1d. 1
A480-a49f: uhci_hcd.
A800-a81f: 0000: 00: 1d. 2
A800-a81f: uhci_hcd.
A880-a89f: 0000: 00: 1A. 0
A880-a89f: uhci_hcd.
Ac00-ac1f: 0000: 00: 1A. 1
Ac00-ac1f: uhci_hcd.
B000-b01f: 0000: 00: 1A. 2
B000-b01f: uhci_hcd.
B400-b407: 0000: 00: 1f. 2
B480-b49f: 0000: 00: 1f. 2
B800-b803: 0000: 00: 1f. 2
B880-b887: 0000: 00: 1f. 2
Bc00-bc03: 0000: 00: 1f. 2
C000-cfff: PCI bus #01
Cc00-cc1f:. 0
D000-dfff: PCI bus #02
Dc00-dc1f:. 0
E000-efff: PCI bus #05
E400-e4ff:. 0
E800-e8ff:. 1
Note that the maximum depth of the tree is 2. Because the master-slave relationship ranges from 2 to 2 at most.
2 I/O interfaces
An I/O interface is a hardware circuit between a group of I/O ports and the corresponding device controller. It acts as a translator, that is, to convert the value in the I/O port to the command and data required by the device. In the opposite direction, it detects device status changes and updates the I/O Ports acting as status registers. You can also use an IRQ line to connect the circuit to the Programmable Interrupt Controller so that it can send an interrupt request on behalf of the corresponding device.
There are two types of interfaces:
2.1 dedicated I/O interfaces
Dedicated for a specific hardware device. In some cases, the device controller and the I/O interface are in the same card (each card must be transferred to an available idle bus slot in the PC. If a cartoon is connected to an external device through an external cable, there is a corresponding connector in the panel behind the PC .). The device connected to the dedicated I/O interface can be an internal device (a device located inside the PC chassis) or an external device (a device located outside the PC chassis ).
There are many types of dedicated I/O interfaces, so there are also many types of devices installed on the PC. We cannot list them one by one. Here we only list some of the most common interfaces:
Keyboard interface:
Connect to a keyboard controller that contains a dedicated microprocessor. The microprocessor decodes the key combination, generates an interrupt, and writes the corresponding Keyboard Scan code into the register.
Graphic interface:
The Controller is encapsulated with the corresponding controller in the graphics card. The graphics card has its own frame buffer, a dedicated processor, and some code stored in the read-only memory (ROM) chip. The frame buffer is a fixed memory on the video card, which stores the Graphical description of the current screen content.
Disk interface:
A cable is connected to the disk controller, which is usually put together with the disk. For example, if an IDE interface is connected to a smart disk controller by a 40-wire cable, this controller can be found on the disk itself.
Bus mouse interface:
A cable connects the interface with the Controller, and the Controller is included in the mouse.
Network Interface:
It is encapsulated with the corresponding controller in the NIC to receive or send network packets. Although there are many widely used network standards, Ethernet (IEEE 802.3) is the most common.
2.2 General I/O interfaces
Used to connect multiple different hardware devices. Devices connected to common I/O interfaces are usually external devices. Modern PCs contain several general I/O interfaces that connect many external devices. The most common interfaces are:
Parallel Port:
Traditionally, it is used to connect to a printer. It can also be used to connect removable disks, scanners, backup devices, and other computers. Data is transmitted in 1 byte (8 bits) each time.
Serial Port:
It is similar to the parallel port, but the data is transmitted by bit. The serial port includes a universal asynchronous transceiver (UART) chip, which can split the byte information to be sent into bit sequences, or reassemble the received bit streams into byte information. Because the serial port speed is essentially lower than the parallel port, it is mainly used to connect external devices that do not require high-speed operations, such as the modem, mouse, and printer.
PCMCIA Interface:
Most portable computers contain this interface. Without restarting the system, external devices in the shape of a credit card can be inserted into or removed from the slot. The most common PCMCIA devices are hard disks, modem, Nic, and extended RAM.
SCSI (Small Computer System Interface) interface:
It is a circuit that connects the PC main bus to the secondary bus (referred to as the SCSI bus. The SCSI-2 bus allows a total of 8 PCs and external devices (hard drives, scanners, CR-ROM recorders, etc.) to connect together. If there are additional interfaces, the broadband SCSI-2 and new SCSI-3 interfaces can allow you to connect up to 16 or more devices. The SCSI standard is the communication protocol used to connect devices through the SCSI bus.
Universal Serial Bus (USB ):
High-speed universal I/O interfaces can be used to connect external devices, instead of traditional parallel ports, serial ports, and SCSI interfaces.
3 device controller
A complex device may need a device controller to drive the device. In essence, controllers play two important roles:
(1) Explain the advanced commands received from the I/O interface, and force the device to perform specific operations by sending an appropriate electrical signal sequence to the device.
(2) convert and adapt the electrical signals received from the device, and modify the value of the Status Register (through the I/O interface.
A typical device controller is a disk controller that receives advanced commands such as writing data blocks from a microprocessor (through an I/O interface, and convert it into low-level disk operations such as "positioning the head on the correct track" and "writing data to this track. The modern disk controllers are quite complex because they can save disk data to the memory cache quickly, and can reschedule CPU advanced requests according to the actual disk's geometric structure, optimize it.
A simple device does not have a device controller. The Programmable Interrupt Controller and the programmable interval timer are similar devices.
Many hardware devices have their own memory, which is usually called I/O shared storage. For example, all new graphics cards have several mb ram in the frame buffer to store the screen images to be displayed on the screen.