What is the io port of the cpu? How is the address space shared?

(1) The Concept of Address

1) Physical Address: The address from the CPU address bus is controlled by hardware circuits. A large portion of the physical address is reserved for memory in the RAM, but it can also be mapped to other types of memory such as video memory or BIOS. After a virtual address in a program instruction undergoes segment and page mapping, a physical address is generated, which is then placed on the CPU's address lines.

The physical address space is partially used for physical RAM and partially for peripheral buses, determined by the system design. In a 32-bit x86 processor, the physical address space is 2^32, i.e., 4GB. However, the actual physical RAM usually cannot reach 4GB because part of the space is allocated to the bus for devices. In PCs, the lower end of the physical address is typically used for RAM, while the higher end is reserved for the bus.

2) Bus Address: The address line or signal generated during the address phase of the bus. Peripherals use the bus address, while the CPU uses the physical address. The relationship between the physical and bus addresses depends on the system design. On the x86 platform, the physical address is the same as the bus address, as they share the same address space. This may be confusing at first, so refer to the section "Independent Addressing" below for more details.

On other platforms, a conversion or mapping might be required. For example, if the CPU needs to access a unit with a physical address of 0xfa000, on the x86 platform, this would generate an access to the 0xfa000 address on the PCI bus because the physical and bus addresses are the same.

3) Virtual Address: Modern operating systems generally use a virtual memory management mechanism, supported by the MMU (Memory Management Unit), which is usually part of the CPU. If the processor lacks an MMU or the MMU is not enabled, the memory address sent by the CPU execution unit is directly transmitted to the chip pins and received by the memory chip (physical memory). This is called a physical address. When the MMU is enabled, the CPU’s address is intercepted by the MMU, and this address is referred to as a virtual address. The MMU translates this into another address that is sent to the external address pins of the CPU, effectively mapping the virtual address to a physical one.

In Linux, a process has 4GB of virtual memory, divided into user space (0–3GB) and kernel space (1GB). Programmers only interact with virtual addresses. Each process has its own private user space, invisible to others. When the CPU issues an instruction fetch request, it uses the virtual address of the current context. The MMU then finds the corresponding physical address from the page table and completes the fetch.

Reading data also involves virtual addresses. For instance, when a program executes “mov ax, var”, the variable “var” is treated as a virtual address during compilation. It is then translated through the page table to find the corresponding physical address, which is used to generate bus timing and complete the data retrieval.

(2) Addressing Methods

1) Addressing Peripherals: Peripherals are accessed by reading and writing to their registers, often referred to as I/O ports. There are two main addressing modes: independent addressing and unified addressing.

Unified Addressing: In this mode, I/O registers are treated like regular memory units. Each port occupies a memory cell, and part of the main memory is used as the I/O address space. For example, in the PDP-11, the top 4KB of main memory was used for I/O device registers. This approach reduces available memory since the I/O ports occupy memory space.

Unified addressing is also known as "I/O memory" mode, where peripheral registers are located in the "memory space." Many peripherals have their own memory buffers, and together with registers, they are collectively referred to as "I/O space."

For example, the Samsung S3C2440, a 32-bit ARM processor, divides its 4GB address space among peripherals, RAM, etc.:

- 0x80001000: LED 8x8 dot matrix address

- 0x48000000 ~ 0x60000000: SFR (special register) address space

- 0x38001002: Keyboard address

- 0x30000000 ~ 0x34000000: SDRAM space

- 0x20000020 ~ 0x2000002e: IDE

- 0x19000300: CS8900

Independent Addressing: In this method, I/O addresses are addressed separately from memory addresses. The I/O port address does not overlap with the memory address range. Thus, a separate I/O address space exists in the system. The CPU must have dedicated I/O instructions (such as IN, OUT) and control logic for input/output operations. Under independent addressing, an address comes from the address bus, and the device cannot distinguish whether it is for I/O or memory. Therefore, the processor uses different control signals (MEMR/MEMW and IOR/IOW) to differentiate between memory and I/O.

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

For the x86 architecture, I/O ports are accessed using the IN/OUT instructions. The PC architecture has 65,536 8-bit I/O ports, forming a 64KB I/O address space ranging from 0~0xFFFF. The I/O address space and the CPU's physical address space are distinct concepts. For example, the I/O address space is 64KB, while a 32-bit CPU's physical address space is 4GB.

You can view the I/O port allocations under Intel 8086+Redhat9.0 by checking /proc/ioports:

0000-001f : dma1

0020-003f : pic1

0040-005f : timer

0060-006f : keyboard

0070-007f : rtc

0080-008f : dma page reg

00a0-00bf : pic2

00c0-00df : dma2

00f0-00ff : fpu

0170-0177 : ide1

...

However, the Intel x86 platform generally uses a technology called Memory Mapped I/O (MMIO), which is part of the PCI specification. The I/O device port is mapped into the memory space. After mapping, the CPU accesses the I/O port as if it were accessing memory. Refer to the typical memory address allocation table for x86/x64 systems provided in the Intel TA 719 documentation:

System resource occupation

BIOS 1M native 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

Depending on the CPU architecture, a system either uses independent or unified addressing. For example, PowerPC and m68k use unified addressing, while x86 uses independent addressing with the concept of I/O space. Most embedded microcontrollers like ARM and PowerPC do not provide I/O space, only memory space, and can be accessed directly via address and pointer. However, the Linux kernel must handle both approaches, so it uses a new method based on I/O mapping or memory mapping. These are commonly referred to as "I/O regions." Regardless of the method, you must first apply for the I/O region using request_resource(), and release it using release_resource().

(III) Summary of Different Architecture Addressing Methods

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

Some architectures, such as PowerPC and m68k, implement only a single physical address space (RAM). In this case, the physical address of the peripheral I/O port is mapped into the CPU's single physical address space, becoming part of the memory. The CPU can then access the peripheral I/O port as if it were a memory unit, without the need for dedicated peripheral I/O instructions. This is known as "memory-mapped."

Other architectures, such as x86, implement a separate address space for peripherals, called "I/O address space" or "I/O port space." This is a separate address space from the CPU's physical RAM address space. All peripheral I/O ports are addressed within this space. The CPU accesses these address units using special I/O instructions (such as x86's IN and OUT instructions). This is called "I/O-mapped." Compared to the RAM physical address space, the I/O address space is usually smaller. 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."

(IV) The Difference Between I/O Port and I/O Memory

During driver writing, the difference between I/O Port and I/O Memory is rarely noticed. Although some non-compliant code can achieve the desired result, it is highly discouraged.

Combined with the following figure, we will thoroughly discuss the relationship between I/O port, I/O memory, and memory. The main memory is 16MB of SDRAM, and the peripheral is a video capture card with a 16MB SDRAM buffer.

1. CPU is the i386 architecture

In the processing of the i386 series, memory and external I/O are independently addressed. The memory space of the MEM is 32 bits, capable of addressing up to 4GB, and the I/O space is 16 bits, capable of addressing up to 64KB.

In the Linux kernel, accessing the I/O port on the peripheral must be done through the I/O port. Accessing I/O memory is more complicated. The external MEM cannot be accessed as main memory, even though the size is comparable, but the external MEM is not registered in the system. To access the external I/O MEM, it must be remapped to the kernel's MEM space before access.

To achieve interface consistency, the kernel provides a mapping function from I/O port to I/O memory. After mapping, the I/O port can be regarded as I/O memory and accessed accordingly.

2. CPU is an ARM or PPC architecture

In this type of embedded processor, the I/O port is addressed by memory mapping, meaning the I/O bus is the MEM bus. If the system's addressing capability is 32 bits, the I/O port + MEM (including I/O memory) can reach 4GB.

When accessing such I/O ports, we can also use I/O port-specific addressing. How the kernel handles I/O port addressing is done during kernel porting. In processors of this architecture, support for I/O port is still maintained, which is a legacy issue from the i386 architecture and will not be discussed here. The way to access I/O memory is the same as i386.

3. I/O Port and I/O Memory Distinction and Contact

Distinguishing between the two involves hardware knowledge. In X86 systems, there are two address spaces: I/O space and memory space, whereas RISC instruction set CPUs (such as ARM, PowerPC, etc.) usually implement only one physical address space, i.e., memory space.

Memory space: The address range of the memory address, a 32-bit operating system has a memory space of 2^32, i.e., 4GB.

I/O Space: A unique space in X86, independent of the internal memory address space, with a 32-bit X86 I/O space of 64KB.

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

I/O memory: When the register or memory is in the memory space, it is called I/O memory.

(V) Summary of Access Methods for I/O Ports and I/O Memory under Linux

1) Accessing I/O Port under Linux

For a given system, it is either independent addressing or unified addressing, depending on the CPU architecture. For example, PowerPC, m68k, etc., use unified addressing, while x86 and others use independent addressing, and there is the concept of I/O space. Most embedded microcontrollers today, such as ARM and PowerPC, do not provide I/O space, only memory space, and can be accessed directly by address and pointer. However, for the Linux kernel, which may be used across different CPUs, it must consider both methods, so it uses a new approach based on I/O mapping or memory mapping. The I/O port is commonly referred to as the "I/O region." Regardless of the method used, you must first apply for the I/O area using request_resource(), and release it at the end using release_resource().

The I/O region is an I/O resource, so it can be described by the resource structure type.

There are two ways to access the I/O port: I/O mapping (I/O-mapped) and memory-mapped (Memory-mapped). The former does not map to memory space, directly using functions such as inb()/outb() to read and write I/O ports; the latter maps I/O ports to I/O memory ("memory space"), and then uses the function that accesses I/O memory to access the I/O port.

1, I/O Mapping Method

Use the I/O port operation function directly: Apply the I/O 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 I/O 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 kernel includes helper functions 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( ), 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" instruction to suspend the CPU.

Insb( ), insw(), 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 {

2. resource_size_t start; / / start of the resource range

3. resource_size_t end; / / end of the resource range

4. const char *name; // name of the resource owner

5. unsigned long flags;// various signs

6. struct resource *parent, *sibling, *child;// Pointer to the father, brother, and child in the resource tree

7. };

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 I/O port to memory for access. When the device is opened or the driver module is loaded, apply for the I/O port area and map to memory using ioport_map(). Then use the I/O memory function for port access. Finally, the device is shut down or the driver module is turned off. Release the I/O 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:

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) Accessing I/O Memory under Linux

The access method of I/O 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() to release the virtual address of the application and release the IO memory resource requested by release_mem_region().

struct resource*request_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 long start, 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 request_mem_region function; size: the size of the space to be mapped; flags: the I/O 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 request_mem_region())

The process is as follows:

3) ioremap and ioport_map

Let's look at the source code of ioport_map and ioport_unmap:

1. void __iomem *ioport_map(unsigned long port, unsigned int nr)

2. {

3. if (port > PIO_MASK)

4. return NULL;

5. return (void __iomem *) (unsigned long) (port + PIO_OFFSET);

6. }

7.

8. void ioport_unmap(void __iomem *addr)

9. {

10. /* Nothing to do */

11. }

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 memory access interface ioread8/iowrite8(...) to access the I/O port.

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

1. unsigned int fastcall ioread8(void __iomem *addr)

2. {

3. IO_COND(addr, return inb(port), return readb(addr));

4. }

5.

6. #define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)

7. #define IO_COND(addr, is_pio, is_mmio) do { \

8. unsigned long port = (unsigned long __force)addr; \

9. if (port < PIO_RESERVED) { \

10. VERIFY_PIO(port); \

11. port &= PIO_MASK; \

12. is_pio; \

13. } else { \

14. is_mmio; \

15. } \

16. } while (0)

17.

18. Expand:

19. unsigned int fastcall ioread8(void __iomem *addr)

20. {

21. unsigned long port = (unsigned long __force)addr;

22. if(port < 0x40000UL) {

23. BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );

24. port &= PIO_MASK;

25. return inb(port);

26. }else{

27. return readb(addr);

28. }

29. }

Vozol Star 12000 Puffs

YIWU JUHE TRADING COMPANY , https://www.nx-vapes.com

Posted on