Document directory
Contents
- Driver
- I/O port
- From smth
- Basic Structure
- Driver
- Implementation
- PCI
- Loopback
- SIS 1, 900
- Implementation of ISA bus DMA
Driver
Linux supports three types of hardware: character devices and block settings.
And network devices. Character devices directly read data without using a buffer. For example, system serial ports/dev/cua0 and/dev/cua1. A block device can only read multiple blocks of a certain size at a time. A block is usually 512 or 1024 bytes. Block devices read and write through the buffer and can read and write randomly. Block devices can be accessed through their device files, but usually through file systems. Only Block devices support mounted file systems. Network devices are accessed through the BSD socket interface.
Linux supports multiple devices. Drivers of these devices share the following features:
* Kernel code: the device driver is part of the system kernel. If a driver error occurs, the entire system may be seriously damaged.
* Kernel interface: the device driver must provide a standard interface for the system kernel or their subsystems. For example, a terminal driver must provide a file I/O interface for the Linux kernel. A SCSI device driver should provide a SCSI device interface for the SCSI subsystem, the SCSI subsystem should also provide file I/O and buffer for the system kernel.
* Kernel mechanisms and services: device drivers use some standard kernel services, such as memory allocation.
* Installable: most Linux device drivers can be imported into the kernel as needed and uninstalled when not needed.
* Configurable: Linux system device drivers can be integrated into the system kernel. You can set the driver during system compilation.
[Directory]
I/O port
Key words: device management, drivers, I/O Ports, resources
Note: This document is released in the spirit of free software open source code. Anyone can obtain, use, and re-release it for free, but you have no right to restrict others from re-publishing your content. The purpose of this article is to hope that it can be useful to readers, but it does not have any guarantees, or even implicit guarantees suitable for a specific purpose. For more details, see the GNU General Public License (GPL) and the GNU Free Documentation protocol (GFDL ).
Almost every type of peripherals is performed by reading and writing registers on the device. 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. 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.
Some architecture CPUs (such as PowerPC and m68k) generally only implement 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. This is the so-called memory ing method (memory-mapped ).
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 ). This is the so-called "I/O ing mode" (I/O-mapped ). 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.
In Linux, I/O Ports Based on I/O ing or memory ing are called "I/O Region" (I/O Region ). Before discussing the management of the I/O Region, let's first analyze how Linux implements the abstract concept of "I/O resources.
3.1 Linux description of I/O resources
Linux designs a general data structure resource to describe various I/O resources (such as I/O Ports, peripheral memory, DMA and IRQ ). This structure is defined in the include/Linux/ioport. h header file:
Struct resource {
Const char * Name;
Unsigned long start, end;
Unsigned long flags;
Struct resource * parent, * sibling, * child;
};
The meaning of each member is as follows:
1. Name Pointer: indicates the name of the resource.
2. start and end: start and end physical addresses of resources. They determine the resource range, that is, a closed interval [start, end].
3. Flags: The identifier that describes the resource attribute (see the following ).
4. pointer parent, sibling, and child: pointer to parent, sibling, and sub-resource respectively.
The flags attribute is a 32-bit flag value of the unsigned long type to describe the attributes of a resource. For example, the type of the resource, whether it is read-only, whether it can be cached, and whether it is occupied. The following is a definition of some common attribute flags (ioport. h ):
/*
* IO resources have these defined flags.
*/
# Define ioresource_bits 0x000000ff/* bus-specific bits */
# Define ioresource_io 0x00000100/* resource type */
# Define ioresource_mem 0x00000200
# Define ioresource_irq 0x00000400
# Define ioresource_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 ioresource_auto 0x40000000
# Define ioresource_busy 0x80000000
/* Driver has marked this resource busy */
Pointer parent, sibling, and child settings are used to manage various I/O resources in the form of a tree.
3.2 Linux I/O Resource Management
Linux uses an inverted tree structure to manage each type of I/O resources (such as I/O Ports, peripheral memory, DMA, and IRQ. Each type of I/O resources corresponds to an inverted Resource Tree. each node in the tree is a resource structure, while the root node of the tree is the whole resource space of the class resources.
Based on the above idea, Linux implements operations such as resource application, release, and search in the kernel/resource. c file.
3.2.1 I/O resource application
Assume that a resource type has the following Resource Tree:
Node root, R1, R2, and R3 are actually a resource structure type. The sub-resources R1, R2, and R3 are linked to a one-way non-circular linked list through the sibling pointer. The table header is defined by the Child pointer in the root node. Therefore, it is also called the sub-resource linked list of the parent resource. The parent pointers of R1, R2, and R3 all point to their parent resource node, which is the root node in the figure.
Assume that you want to allocate an I/O resource to the root node (represented by the shadow area in the figure ). The request_resource () function implements this function. It has two parameters: ① root pointer, indicating the resource root node to be allocated; ② new pointer, pointing to the resource to be allocated (that is, the shadow area in the figure) resource structure. 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 (& resource_lock );
Return conflict? -Ebusy: 0;
}
Note the preceding functions as follows:
① The resource lock resource_lock is used to read and write all resource trees. Any code segment must hold the lock before accessing a Resource Tree. Its definition is as follows (kernel/resource. C ):
Static rwlock_t resource_lock = rw_lock_unlocked;
② We can see that the function actually completes the actual resource allocation by calling the internal static function _ request_resource. If this function returns a non-null pointer, a resource conflict exists. Otherwise, if null is returned, the allocation is successful.
③ Finally, if the conflict pointer is null, The request_resource () function returns 0 to indicate success; otherwise, return-ebusy to indicate that the resource to be allocated is occupied.
Function _ request_resource () completes the actual resource allocation. If some or all of the resources described by the new parameter are occupied by other nodes, the function returns a pointer to the resource structure in conflict with the new parameter. Otherwise, null is returned. The source code of this function is as follows:
(Kernel/resource. C ):
/* Return the conflict entry if you can't 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 <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;
}
}
Note for the function:
① The first three if statements determine whether the resource range described by new is included in the root, and whether it is a valid Resource (because the end must be greater than start ). Otherwise, the root pointer is returned, indicating a conflict with the root node.
② Next we will use a for loop to traverse the root node's root child linked list to check whether there are resource conflicts, insert new to the appropriate position in the Child linked list (the Child linked list is arranged in the ascending order of the physical addresses of I/O resources ). Therefore, it uses the TMP pointer to point to the currently being scanned resource structure, and the pointer P to the sibling pointer member variable of the previous resource structure. The initial value of P is to root-> sibling. The steps for executing the for loop body are as follows:
L point TMP to the resource structure currently being scanned (TMP = * P ).
L determine whether the TMP pointer is null (if the TMP pointer is null, it indicates that the entire child linked list has been traversed), or whether the start position of the currently scanned node is larger than the end position of the new node. As long as one of the two conditions is true, there is no resource conflict, so we can link new to the Child linked list: ① set the new sibling pointer to the node TMP (New-> sibling = TMP) currently being scanned ); ② The sibling pointer of the previous brother node of the current node TMP is changed to point to the new node (* P = new); ③ The new parent pointer is set to point to the root node. Then the function can return (Return Value null indicates there is no resource conflict ).
L if neither of the preceding conditions is true, it indicates that the resource domains of the currently scanned nodes may conflict with new (in fact, there is an intersection between the two closed intervals), so further judgment is required. For this reason, it first modifies the pointer P and points it to TMP-> sibling, so as to continue scanning the child linked list. Then, judge whether TMP-> end is smaller than New-> start. If it is smaller than, it indicates that the current node TMP and new have no resource conflicts. Therefore, execute the continue statement to continue scanning the child linked list. Otherwise, if TMP-> end is greater than or equal to new-> start, there is an intersection between TMP-> [start, end] and new-> [start, end. Therefore, the pointer TMP of the current node is returned, indicating a resource conflict.
3.2.2 release of resources
The release_resource () function is used to release I/O resources. This function has only one parameter, that is, the pointer old, pointing to the resource to be released. The source code is as follows:
Int release_resource (struct resource * old)
{
Int retval;
Write_lock (& resource_lock );
Retval = _ release_resource (old );
Write_unlock (& resource_lock );
Return retval;
}
We can see that it actually calls the internal static function _ release_resource () to release the actual resource. The main task of function _ release_resource () is to remove the resource region old (if it already exists) from its parent resource's child linked 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;
& Nbsp