(1) The concept of address

1) Physical address: The address from the CPU address bus is controlled by the hardware circuit. A large part of the physical address is reserved for memory in the memory stick, but it is also often mapped to other memory (such as video memory, BIOS, etc.). After the virtual address in the program instruction is segment mapped and page mapped, a physical address is generated, and the physical address is placed on the address line of the CPU.


The physical address space, part of which is used for physical RAM (memory) and part for bus, which is determined by hardware design. Therefore, in the x86 processor of 32bits address line, the physical address space is 2 to the power of 32, that is, 4GB. However, the physical RAM can't usually go up to 4GB, because there is still a part to be used for the bus (there are many other devices on the bus). In the PC, the low-end physical address is generally used for the RAM, and the high-end physical address is used for the bus.

2) Bus address: The address line of the bus or the signal generated during the address period. The peripheral uses the bus address and the CPU uses the physical address.

The relationship between the physical address and the bus address is determined by the design of the system. On the x86 platform, the physical address is the bus address, because they share the same address space - this sentence is a bit difficult to understand, see "Independent Addressing" below. On other platforms, conversion/mapping may be required. For example, if the CPU needs to access a unit whose physical address is 0xfa000, then on the x86 platform, a visit to the 0xfa000 address on the PCI bus is generated. Because the physical address and bus address are the same.

3) Virtual Address: The modern operating system generally adopts the Virtual Memory Management mechanism, which requires the support of the MMU (Memory Management Unit). The MMU is usually part of the CPU. If the processor does not have an MMU, or if there is an MMU but is not enabled, the memory address sent by the CPU execution unit will be directly transmitted to the chip pins and received by the memory chip (physical memory). This is called the physical address. (Physical Address), if the MMU is enabled on the processor, the memory address issued by the CPU execution unit will be intercepted by the MMU. The address from the CPU to the MMU is called the Virtual Address, and the MMU translates this address into another address. Go to the external address pin of the CPU chip, that is, map the virtual address to a physical address.

In Linux, the 4GB (virtual) memory of a process is divided into user space and kernel space. The user space is 0~3GB (ie PAGE_OFFSET, which is equal to 0xC0000000 in 0X86), and the remaining 1G is kernel space. Programmers can only use virtual addresses. Each process in the system has its own private user space (0 to 3G), which is invisible to other processes in the system.

The address when the CPU issues the instruction fetch request is the virtual address of the current context, and the MMU finds the physical address of the virtual address from the page table, and completes the fetching. Also read the data is also a virtual address, such as mov ax, var. Compile time var is a virtual address, but also through the MMU from the table to find the physical address, and then generate bus timing, complete the data.

(2) Addressing method

1) Addressing peripherals

Peripherals are performed by reading and writing registers on the device. Peripheral registers are also called "I/O ports", while IO ports are available in two addressing modes: independent addressing and unified programming.

Unified Addressing: The IO registers (ie IO ports) in the peripheral interface are treated the same as the main memory unit. Each port occupies the address of a memory cell, and part of the main memory is used as the IO address space, eg, in the PDP. In -11, the highest 4K main memory is used as the IO device register address. The port occupies the address space of the memory, so that the storage capacity is reduced.

Unified addressing is also known as "I/O memory" mode, and peripheral registers are located in "memory space" (many have their own memory, buffers, peripheral registers and memory collectively called "I/O space").

For example, Samsung's S3C2440 is a 32-bit ARM processor. Its 4GB address space is divided by peripherals, RAM, etc.: 0x8000 1000 LED 8*8 dot matrix address 0x4800 0000 ~ 0x6000 0000 SFR (special register) address space 0x3800 1002 Keyboard address 0x3000 0000 ~ 0x3400 0000 SDRAM space 0x2000 0020 ~ 0x2000 002e IDE0x1900 0300 CS8900

Independent Addressing (Single Addressing): The IO address is independently addressed from the memory address. The I/O port address does not occupy the address range of the memory space. Thus, another IO address independent of the storage address exists in the system. The CPU must also have IO instructions (IN, OUT, etc.) and control logic dedicated to input and output operations. Under independent addressing, an address comes from the address bus. The device does not know whether it is for the IO port or the memory. Therefore, the processor implements the I/O port and memory through the two control signals of MEMR/MEMW and IOR/IOW. Different addressing. For example, intel80x86 is addressed separately, CPU memory and I/O are addressed together, that is, the address and I/O address of a part of the memory overlap.

Independent addressing is also referred to as the "I/O port" mode, and the peripheral registers are located in the "I/O (address) space".

For the x86 architecture, access is via the IN/OUT instruction. The PC architecture has a total of 65536 8-bit I/O ports, which make up 64K I/O address spaces, numbered from 0~0xFFFF, with 16 bits, and 80x86 is addressed by the lower 16-bit address lines A0-A15. Two consecutive 8-bit ports can form a 16-bit port, and four consecutively form a 32-bit port. The I/O address space and the physical address space of the CPU are two different concepts . For example, the I/O address space is 64K, and a 32-bit CPU physical address space is 4G. For example, under Intel 8086+Redhat9.0, use "more/proc/ioports" to see: 0000-001f: dma10020-003f: pic10040-005f: timer0060-006f: keyboard0070-007f: rtc0080-008f: dma page reg00a0- 00bf : pic200c0-00df : dma200f0-00ff : fpu0170-0177 : ide1......

However, the Intelx86 platform generally uses a technology called Memory Map (MMIO), which is part of the PCI specification. The IO device port is mapped to the memory space. After mapping, the CPU accesses the IO port as if it were accessing memory. See the typical memory address allocation table for x86/x64 systems given in the IntelTA 719 documentation:

System resource occupation

BIOS 1M

Local APIC 4K

Chipset reserved 2M

IO APIC 4K

PCI device 256M

PCI Express device 256M

PCI device (optional) 256M

Display frame buffer 16M

TSEG 1M

For a given system, it is either independent addressing or unified addressing, depending on the architecture of the CPU. For example, PowerPC, m68k, etc. use unified addressing, while X86 and so on use independent addressing, and there is the concept of IO space. At present, most embedded microcontrollers such as ARM and PowerPC do not provide I/O space, only memory space, and can be accessed directly by address and pointer. But for the Linux kernel, it may be used for different CPUs, so it must be considered in both ways, so it uses a new method that will be based on I/O mapping or memory mapping. The O port is commonly referred to as the "I/O region". Regardless of which method you use, you must first apply for the IO area: request_resource(), and release it at the end: release_resource().

(III) Summary of different architecture addressing methods

Almost every peripheral is done by reading and writing to a register on the device. Peripheral registers, also known as "I/O ports", typically include three categories: control registers, status registers, and data registers, and the registers of a peripheral are typically addressed consecutively. The CPU addresses the physical address of the peripheral IO port in two ways: one is I/O mapping (I/O-mapped), and the other is memory-mapped. Which one is used depends on the architecture of the CPU.

Some architecture CPUs (eg, PowerPC, m68k, etc.) typically implement only one physical address space (RAM). In this case, the physical address of the peripheral I/O port is mapped into a single physical address space of the CPU and becomes part of the memory. At this point, the CPU can access the peripheral I/O port as if it were a memory unit, without the need to set up a dedicated peripheral I/O instruction. This is called " memory -mapped".

Other architecture CPUs (typically X86) specifically implement a separate address space for the peripherals, called "I/O address space" or "I/O port space." This is an address space that is different from the physical RAM address space of the CPU. All peripheral I/O ports are addressed in this space. The CPU accesses address units (that is, I/O ports) in this space by setting up special I/O instructions (such as X86's IN and OUT instructions). This is called "I / O mapping mode" (I / O-mapped) . Compared with the RAM physical address space, the I/O address space is usually small. For example, the x86 CPU's I/O space is only 64KB ( 0-0xffff ). This is a major drawback of the "I/O mapping approach."

Linux refers to I/O ports based on I/O mapping or memory mapping as "I/O regions". Before discussing the management of I/O areas, let's first analyze how Linux implements the abstract concept of "I/O resources."

(four) the difference between IO port and IO memory

During the driver writing process, the difference between IO Port and IO Mem is rarely noticed. Although the use of some non-compliant code can achieve the ultimate goal, it is extremely deprecated.

Combined with the following figure, we thoroughly talk about the relationship between IO port and IO memory and memory. The main memory is 16 Mbytes of SDRAM, and the peripheral is a video capture card with a 16 Mbyte SDRAM as a buffer.

1. The CPU is the case of the i386 architecture

In the processing of the i386 series, memory and external IO are independently addressed and independently addressed. The memory space of the MEM is 32 bits that can be addressed to 4G, and the IO space is 16 bits that can be addressed to 64K.

In the Linux kernel, the IO Port on the access peripheral must be addressed by the IO Port. Access to IO Mem is more confusing, the external MEM can not be accessed as the main memory, although the size is comparable, but the external MEM is not registered in the system. Access to the external IO MEM must be mapped to the kernel's MEM space by remap before access.

In order to achieve the identity of the interface, the kernel provides a mapping function from IO Port to IO Mem. After mapping, the IO Port can be regarded as IO Mem, which can be accessed according to IO Mem.

2. The CPU is an ARM or PPC architecture

In this type of embedded processor, the IO Port is addressed by memory mapping, that is, the IO bus is the Mem bus. If the addressing capability of the system is 32 bits, IO Port+Mem (including IO Mem) can reach 4G.

When accessing such IO Ports, we can also use IO Port dedicated addressing. As for how to complete the kernel when addressing the IO Port, this is done at the time of kernel porting. In the processor of this architecture, the support of IO Port is still maintained, which is completely a problem left by the i386 architecture, and will not be discussed here. The way to access IO Mem is the same as i386.

3, IO port and IO memory distinction and contact

How to distinguish between the two involves hardware knowledge. In the X86 system, there are two address spaces: IO space and memory space, while the CPU of the RISC instruction system (such as ARM, PowerPC, etc.) usually only implements one physical address space, that is, memory. space. Memory space: The address range of the memory address, the 32-bit operating system memory space is 2 to the 32nd power, that is, 4G. IO space: A space unique to X86, and an address space independent of memory space, 32-bit X86 has 64K IO space.

IO port: When the register or memory is in IO space, it is called IO port . General registers are also commonly known as I/O ports, or I/Oports. This I/O port can be mapped to a MemorySpace or mapped to an I/OSpace.

IO memory : When the register or memory is in the memory space, it is called IO memory .

(5) Summary of access methods for IO ports and IO memory under Linux

1) Access IO port under Linux

For a given system, it is either independent addressing or unified addressing, depending on the architecture of the CPU. For example, PowerPC, m68k, etc. use unified addressing, while X86 and so on use independent addressing, and there is the concept of IO space. At present, most embedded microcontrollers such as ARM and PowerPC do not provide I/O space, only memory space, and can be accessed directly by address and pointer. But for the Linux kernel, it may be used for different CPUs, so it must be considered in both ways, so it uses a new method that will be based on I/O mapping or memory mapping. The O port is commonly referred to as the "I/O region". Regardless of which method you use, you must first apply for the IO area: request_resource(), and release it at the end: release_resource().

The 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 mapping (I/O-mapped) and memory-mapped (Memory-mapped) . The former approach does not map to memory space, directly using functions such as intb()/outb() to read and write IO ports; the latter MMIO maps IO ports to IO memory ("memory space"), and then Use the function that accesses IO memory to access the IO port.

1, I / O mapping method

Use the IO port operation function directly: Apply the IO port area when the device is opened or the driver module is loaded, then use inb(), outb(), etc. for port access, and finally release the IO port range when the device is shut down or the driver is unloaded.

The in, out, ins, and outs assembly language instructions all have access to the I/O port. The following helper functions are included in the kernel to simplify this access:

Inb( ), inw( ), inl( ) read 1, 2 or 4 consecutive bytes from the I/O port, respectively. The suffixes "b", "w", and "l" represent one byte (8 bits), one word (16 bits), and one long integer (32 bits), respectively.

Inb_p( ), inw_p( ), inl_p( ) read 1, 2 or 4 consecutive bytes from the I/O port, respectively, and then execute a "dummy" command to suspend the CPU.

Outb( ), outw( ), and outl( ) write 1, 2, or 4 consecutive bytes to an I/O port, respectively.

Outb_p( ), outw_p( ), and outl_p( ) write 1, 2, or 4 consecutive bytes to an I/O port, respectively, and then execute a "dummy" instruction to suspend the CPU.

Insb( ), insw( ), and insl( ) read a sequence of consecutive bytes in groups of 1, 2, or 4 bytes from the I/O port, respectively. The length of the byte sequence is given by the parameters of the function.

Outsb( ), outsw( ), and outsl( ) respectively write a sequence of consecutive bytes in groups of 1, 2, or 4 bytes to the I/O port.

The process is as follows:

While accessing I/O ports is simple, detecting which I/O ports are already assigned to I/O devices may not be as simple as this, especially for ISA bus-based systems. In general, I/O device drivers need to blindly write data to an I/O port in order to probe hardware devices; however, if other hardware devices already use this port, the system will crash. To prevent this from happening, the kernel must use "resources" to record the I/O ports assigned to each hardware device. A resource represents a part of an entity that is distributed to the device driver in a mutually exclusive manner. Here, the resource represents a range of I/O port addresses. The information corresponding to each resource is stored in the resource data structure:

Struct resource {

Resource_size_t start;// start of resource range

Resource_size_t end;// end of 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

};

All of the same resources are inserted into a tree data structure (father, sibling, and child); for example, all resources representing the I/O port address range are included in a tree whose root node is ioport_resource. The children of the node are collected in a linked list whose first element is pointed to by child. The sibling field points to the next node in the list.

Why use a tree? For example, consider the I/O port address used by the IDE hard disk interface - for example, from 0xf000 to 0xf00f. Then, such a resource whose start field is 0xf000 and whose end field is 0xf00f is contained in the tree, and the normal name of the controller is stored in the name field. However, the IDE device driver needs to remember additional information, that is, the IDE chain master uses a subrange of 0xf000 to 0xf007, and the slave uses a subrange of 0xf008 to 0xf00f. In order to do this, the device driver inserts the children corresponding to the two sub-ranges into the resources corresponding to the entire range from 0xf000 to 0xf00f. In general, each node in the tree must be equivalent to a sub-range of the corresponding range of the parent node. 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, the parameters passed to them are the root node of the resource tree and the address of the new resource data structure to be inserted:

Request_resource() //Assign a given range to an I/O device.

Allocate_resource() // Look for a usable range of the given size and arrangement in the resource tree; if it exists, assign this range to an I/O device (mainly used by the PCI device driver, you can use any port number and The memory address on the motherboard is configured for it).

Release_resource() //Release the given range previously assigned to the I/O device.

The kernel also defines some shortcut functions for the I/O ports for the above functions: request_region( ) allocates the given range of I/O ports, and release_region( ) releases the range previously allocated to I/O ports. The tree of all I/O addresses currently assigned to I/O devices is available from the /proc/ioports file.

2, memory mapping

  

Map the IO port to memory for access. When the device is opened or the driver module is loaded, apply for the IO port area and map to memory using ioport_map(). Then use the IO memory function for port access. Finally, the device is shut down or the driver module is turned on. Release the IO port and release the map when it is unloaded .

The prototype of the mapping function is: void *ioport_map(unsigned long port, unsigned int count);

Through this function, the count consecutive I/O ports from the beginning of the port can be remapped into a "memory space". You can then access these I/O ports at the address they return, just like accessing I/O memory. Note, however, that I/O ports must also be allocated via request_region() before mapping.

When this mapping is no longer needed, you need to call the following function to undo:

Void ioport_unmap(void *addr);

After the physical address of the device is mapped to the virtual address, although these addresses can be accessed directly through the pointer, the following set of functions of the Linux kernel should be used 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); The earlier version of the function corresponding to the above function is (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(u16 value, void *addr);void iowrite32(u32 value, void *addr) The earlier version of the function corresponding to the above function is (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:

2) Access IO memory under Linux

The access method of IO memory is: first call request_mem_region() to apply for resources, then map the register address to the virtual address of kernel space through ioremap(), then access the registers by Linux device access programming interface. After the access is completed, use ioremap () Release the virtual address of the application and release the IO memory resource requested by release_mem_region().

Struct resource*requset_mem_region(unsigned long start, unsigned long len, char *name);

This function requests len memory addresses (virtual addresses between 3G and 4G) from the kernel, where start is the I/O physical address and name is the name of the device . Note that if the assignment is successful, it returns non-NULL, otherwise it returns NULL. In addition, you can use /proc/iomem to view the memory range of the system for various devices.

To release the requested I/O memory, you should use the release_mem_region() function:

Void release_mem_region(unsigned longstart, unsigned long len)

After applying a set of I/O memory, call the ioremap() function :

Void* ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);

The meaning of three parameters is: phys_addr: the same I/O physical address as the parameter start in the requset_mem_region function; size: the size of the space to be mapped; flags: the IO space to be mapped and the permissions-related flags;

Function: Map an I/O address space to the virtual address space of the kernel (applied by requset _mem_region())

The process is as follows:

3) ioremap and ioport_map

Let's take a look at the source code of ioport_map and ioport_umap:

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 simply adds PIO_OFFSET(64k) to the port, while ioport_unmap does nothing. In this way, the 64k space of portio is mapped to 64k~128k of the virtual address, and the virtual address returned by ioremap is definitely above 3G . The purpose of the ioport_map function is to attempt to provide a virtual address space consistent with ioremap. Analysis of the source code of ioport_map() reveals that the so-called mapping to memory space behavior is actually an "illusion" created for developers, and is not mapped to the kernel virtual address, just to allow engineers to use unified I / O The memory access interface ioread8/iowrite8(...) accesses the I/O port.

Finally, look at the source code of ioread8, its implementation is to judge the virtual address to distinguish between IO port and IO memory, and then use inb/outb and readb/writeb to read and write.

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);

}

}

Basic Physics Experiment Instrument Series

Basic physics experiment instrument series, used in physics laboratories of colleges and universities.

Basic Physics Experiment Instrument,Light And Optical Instruments,Optical Viewing Instrument,Microscope Light Source Instrument

Yuheng Optics Co., Ltd.(Changchun) , https://www.yhenoptics.com