Microcomputer Bus Address
Address Bus
-
Explanation from Baidu Encyclopedia: The address bus (Address Bus; also known as: address bus) is a part of a computer bus, used by the CPU or units with DMA capabilities to communicate the physical addresses of memory components/locations that these units want to access (read/write). -
Address Bus = the range of memory accessible by the CPU: To explain the address bus with a phenomenon: Installed with a 32-bit Windows 7 system, obviously with 8GB of memory, but the system only recognizes 3.8GB. It can only recognize 8GB when installed with 64-bit. 32-bit can represent/access 4,294,967,296 bits.
kbit——mbit——gbit | Difference of 1024 |
---|---|
bit | 4,294,967,296 |
kbit | 4,194,304 K |
mbit | 4,096 M |
gbit | 4 G |
-
Address Bus = The CPU finds external memory units relying on the data transmitted by the address bus. If the CPU has 8 address lines, transmitting 0 or 1 on each line, then the range of transmitted data is 00000000~ 11111111
. Each value corresponds to a memory unit in memory, so it can find the memory unit numbered00000000~ 11111111
. If the transmitted data is00110011
, it will find the memory unit00110011
. If the transmitted data is10110111
, it will find the memory unit10110111
. The CPU cannot find memory units not in the range[00000000, 11111111]
, for example, the CPU cannot find the memory unit100000000
. The addressing ability is the calculation of how many memory units the CPU can find,00000000~11111111
has a total of 256 units, and the size of one memory unit is 1byte, so the total size of these 256 memory units is 256byte. -
The CPU specifies storage units through the address bus. -
**== The address bus determines the maximum size of memory space that the CPU can access ==. e.g.: 10 address lines can access a maximum memory of 1024
** (2 to the power of 10) bits of binary data (1B) -
The address bus is the sum of the number of address lines. If the CPU’s address bus width is 32 bits, then the CPU’s addressing range is 4GB (2 to the power of 32 bits), so it supports a maximum of 4GB of memory. -
For example, in the phenomenon mentioned above: installed with a 32-bit Windows 7 system, obviously with 8GB of memory, but the system only recognizes 3.8GB, it can only recognize 8GB when installed with 64-bit. The CPU’s access range with a 32-bit operating system is 2^32 bits, which is 4194304 kbit, which is 4096 Mbit, equal to 4GB. The Raspberry Pi is also 32-bit, with 1GB of memory, but it can only access 949MB, with the rest repurposed.
Data Bus
-
The CPU addresses through the address bus, then exchanges information with external devices through the data bus. -
It is the channel for data transfer between the CPU and memory or other devices. -
The width of the data bus determines the speed of data transfer between the CPU and the external world. -
Each transmission line can only transmit 1 bit of binary data at a time. e.g.: 8 data lines can transmit an 8-bit binary data (which is one byte) at once. -
The data bus is the sum of the number of data lines, and the number of bits of the data bus determines the amount of information the CPU can exchange in a single communication.
The Impact of Data Bus Width on CPU Performance
-
Firstly, the speed of the bus (i.e. the CPU’s main frequency, one of the performance metrics of the CPU) determines the speed of information exchange between the CPU and peripherals. -
Secondly, the width of the data bus is also one of the parameters representing CPU performance (usually, when we say “64-bit CPU”, we mean that the width of the CPU’s data bus is 64 bits). For example, a CPU with a 64-bit data bus can retrieve 64 bits of data at once, while a CPU with an 8-bit data bus can only retrieve 8 bits of data at once. At the same frequency, a CPU with an 8-bit data bus must continuously retrieve data 8 times for the data amount to match that of a 64-bit data bus, so the performance difference in data retrieval alone is 8 times. Moreover, typically, the number of bits in the CPU’s registers is the same as the width of the data bus, so in terms of data processing, a 64-bit CPU is much faster than an 8-bit CPU. -
The number of bits in the CPU’s address bus and data bus can be different (a typical representative is the 51 microcontroller), but generally, they are the same. A 16-bit machine has 16 data lines and 20 address lines, able to access 1MB (2 to the power of 20), a 32-bit machine has 32 data lines and 32 address lines, able to access 4GB (2 to the power of 32), and a 64-bit machine indeed has 64 data lines.
Physical Address (PA)
-
Explanation from Baidu Encyclopedia: The physical address of the network card is the actual address corresponding to the storage unit in the memory, which is called the physical address, corresponding to the logical address. The physical address of the network card is usually written into the network card’s EPROM (a type of flash memory chip that can usually be erased by a program) by the manufacturer of the network card, and it stores the actual address used to identify the computer sending the data and the host receiving the data during data transmission.
-
The physical address mentioned here is the actual address of the memory unit in memory, not the address of other electronic components connected by external buses! **== The physical address is relatively easy to understand; the physical address is the number of each memory unit in memory ==**. This number is sequentially arranged, and the size of the physical address determines how many memory units are in memory, and the size of the physical address is determined by the width of the address bus! The physical address is the hardware’s actual address or absolute address
Virtual Address (VA)
-
The virtual address is the logical address used by Windows programs running in 386 protected mode, which is the address used by the program to access the memory ==logical address (based on algorithm; software-level address: fake address)==, similar to the segmented address in real address mode; virtual addresses can also be written in the form of “segment: offset”, where the segment refers to the segment selector. Linux does not have various protected modes; it originally uses virtual addresses. -
The virtual address is a concept in the CPU’s protected mode. The protected mode is an operating mode of x86-compatible CPUs in the 80286 series and later. After the CPU finishes booting the operating system kernel, the operating system kernel enters a kind of CPU protected mode, also called virtual memory management. After this, programs run in virtual memory, and all addresses in virtual memory are indirect, so sometimes you can see a virtual address corresponding to different physical addresses. For example, the entry virtual address of the call function in process A is 0x001, and B is also 0x001, but they correspond to different physical addresses. The operating system adopts this memory management method. -
It prevents programs from writing data to physical addresses, causing unnecessary problems. For example, if the physical address of process A is known, writing data to this address will cause problems in process A. Programs running in virtual memory never know which physical address segment they are in! Now, even if the operating system is running in protected mode, even if the physical address of other processes is known, writing to it is not allowed! However, it is possible to obtain all control permissions of the virtual address space of that process and write specified data through backdoor functions left by the operating system. -
Virtual memory management uses a method of “robbing Peter to pay Paul”, so the memory of virtual memory is much larger than physical memory. Before entering virtual mode, the CPU and Bootloader (the BootLoader runs before the operating system kernel runs. It can initialize hardware devices, establish memory space mapping, so as to bring the system’s software and hardware environment to an appropriate state to prepare for the correct environment for the final call to the operating system kernel) run in real mode, directly operating on physical addresses! Virtual memory also has paging management, which is to ensure that memory fragmentation does not occur. After the operating system kernel initializes the page table in memory, the CPU’s paging flag will be set, which is for the MMU to see! -
MMU
is the abbreviation for Memory Management Unit, which is the control line used in the central processing unit (CPU) to manage virtual memory and physical memory, and is also responsible for mapping virtual addresses to physical addresses and providing hardware mechanisms for memory access authorization in multi-user, multi-process operating systems. Its functions are twofold: address translation and memory protection. <== The MMU translates virtual addresses into physical addresses. ==
Blog post introducing various addresses:
Differences between physical addresses, virtual addresses, and bus addresses
Page Table (MMU Unit)
Paging Management:
-
Memory paging is actually what we call 4GB of space. All memory is divided by the operating system kernel into pages of 4GB when our program runs. Although it is said to have 4GB, there is not really a 4GB space. A small portion of the 4GB space is mapped to physical memory, or mapped to files on the hard disk (fopen), or not mapped. There is also a large part of the memory that is not mapped, and the same applies to physical memory, which is also paged to create a mapping relationship with virtual memory. The algorithm that maps virtual addresses to physical addresses (page table) determines which location the virtual address is mapped to. The page table is managed by the MMU (Paging Memory Management Unit), which executes the mapping of virtual addresses to physical addresses after the page table is designed. -
In fact, under normal circumstances, there is only 3GB of user space. If your memory is 4GB, then 1GB is used by the operating system kernel. The so-called 4GB space is just an illusion created by the operating system, based on the form of virtual memory management, making it feel like every process has 4GB of usable space! Here, we talk about the “robbing Peter to pay Paul” method. When our program is loaded into the 4GB space, it actually cannot use all of the so-called 4GB space. A large portion of memory is idle. When other programs are loaded and find that there is not enough memory, they take the idle part from other programs’ 4GB space to use for this process. Conversely, when this process does not have enough memory, it takes the idle space from other processes to use for this process. -
When we need to operate on physical addresses, such as when an if statement needs to jump based on the CPU’s status register, we must know its physical address. There is an electronic component in memory called the MMU, which is responsible for querying and converting the physical address corresponding to the virtual address from the memory mapping table initialized by the operating system. For example, mov 0x4h8 is a virtual address. When we want to write data to this virtual address, the MMU first checks whether the flag state of the CPU’s paging status register is set. If it is set, the MMU captures this virtual address and queries the corresponding physical address in the memory mapping table initialized by the operating system, converting it to the actual physical address, and then provides this actual physical address to the CPU to execute the corresponding command. Conversely, when the CPU reads data from memory, for example, when process A wants to read data from a certain virtual address in memory, the instruction in process A gives the virtual address. The MMU first checks whether the flag in the CPU’s paging status register is set. If it is set, the MMU captures the virtual address and converts it to the corresponding physical address, then submits it to the CPU, which accesses the data in memory!
For more detailed address issues, see here.
BCM2835 Chip Manual
Below is an excerpt of a figure from the Raspberry Pi chip manual:
BCM2835 is the model of the Raspberry Pi 3B CPU, it is ARM-cortexA53 architecture, the CPU Bus is the address bus, 00000000~FFFFFFFF
is the CPU’s addressing range (4GB). DMA is a high-speed copy unit, the CPU can activate DMA to directly perform data copying, which is a direct memory access unit. Physical Address (PA) 1GB, Virtual Address (VA) 4GB. If a program is larger than the physical address of 1GB, can it not run? No, it has an MMU unit that maps physical addresses to virtual addresses, and the code we operate is basically in virtual addresses, and it has a mapping page table (mentioned above).
-
By understanding the GPIO of the Raspberry Pi through the chip manual: there are 54 general-purpose I/O GPIO lines, divided into two rows. The spare functions are usually peripheral IO and can appear in each bank with a peripheral device to allow flexible selection of IO voltage.
-
GPIO has 41 registers, all accesses are 32 bits.
-
Description is the function description of the register. GPFSEL0 (register name)
GPIO Function Select 0
(Function Select: Input or Output); GPSET0 (register name)GPIO Pin Output Set 0
(Set IO port to 0); GPSET1 (register name)GPIO Pin Output Set 1
(Set IO port to 1); GPCLR0 (register name)GPIO Pin Output Clear 0
(Clear 0). The address below is: bus address (not the actual physical address).
-
FSELn represents GPIOn,
The image below gives an example of function selection for the ninth pin, configuring registers 29-27 to set the respective functions. According to the image below, register 0 indicates that 0~9 uses register 0.
-
The output set register is used to set GPIO pins. The SET{n} field defines the respective GPIO pins to be set, writing “0” to the field has no effect. If the GPIO pin is used in input mode (by default), the value in the SET{n} field will be ignored. However, if the pin is then defined as output, the bit will be set according to the last set/clear operation. Separating set and clear functions eliminates the need for read-modify-write operations. GPSETn register is used to set the IO port to 1, setting the fourth pin, which is the fourth bit of the register.
-
The output clear register is used to clear GPIO pins. The CLR{n} field defines which GPIO pins to clear, writing “0” to the field has no effect. If the pin is in input mode (default), then the value in the CLR{n} field is ignored. However, if the pin is then defined as output, the bit will be set according to the last set/clear operation. Separating set and clear functions eliminates the need for read-modify-write operations. GPCLRn is the clear function register.
Configuring the Raspberry Pi’s pin4 as an output pin:
Function select Output/Input (GPIO Function Select Registers) 32 bits 14-12 001 = GPIO Pin4 is an output
Just set bits 14-12 of the GPFSL0 register to 001. Just left shift 0x6 (which corresponds to binary 110) by 12 bits, then invert and AND with GPFSL0 to set bits 13 and 14 to 0, then left shift 0x6 (which corresponds to binary 110) by 12 bits and OR with GPFSL0 to set bit 12 to 1.
-
Use the copy_from_user() function in the driver code to read user input instructions, and use the copy_to_user() function to let the pin feedback its current state, allowing the user to read it.
If you want to find the Raspberry Pi pin, click here.
Raspberry Pi IO Control Driver Code:
ioremap, iounmap
1. Generally, our peripherals are accessed by reading and writing registers on the device, usually including control registers, status registers, and data registers. The registers of peripherals are usually continuously addressed, and depending on the CPU architecture, there are two ways for the CPU to address IO ports:
-
IO mapped method (IO-mapped): A typical example is that the X86 processor has a separate address space for peripherals, called “IO port space” or “IO address space”. At this time, the CPU can access this “IO port space” through specific instructions (such as IN and OUT in X86). -
Memory-mapped method (memory-mapped): RISC instruction system CPUs generally implement only one physical address space, and the peripheral IO ports become part of memory. At this time, the CPU can access the peripheral IO ports as conveniently as accessing its own memory, without having to set up specific instructions to access them. In driver development, the memory-mapped method is generally used.
2. In driver development, generally speaking, the physical address of the peripheral IO memory resources is known, determined by hardware design. However, the CPU does not pre-assign virtual address values for these known peripheral IO memory resources, so the driver program cannot directly access the IO memory through the physical address of the peripheral, but must map it to the virtual address space (through the page table) before accessing these IO memory using memory instructions based on the kernel-mapped virtual address.
3. In the Linux kernel’s io.h header file, the ioremap() function is declared to map IO memory resources to the core virtual address space (3GB~4GB). Of course, if not needed, it can be unmapped using iounmap(). These two functions are in mm/ioremap.c file:
Start mapping: void* ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags) // Mapping a device means associating a segment of user space address with device memory, allowing the program to read or write within the allocated address range, which is in fact accessing the device. The first parameter is the starting address of the mapping, the second parameter is the length of the mapping, the third parameter depends on your hardware characteristics. For example, if you only map a 32-bit register, a length of 4 is sufficient. (Here, the Raspberry Pi IO port function setting register and IO port setting register are both 32-bit registers, so allocating four bytes is enough.) For example: GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4); GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4); GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4); these three lines set the register addresses, and the volatile keyword ensures that this instruction will not be omitted due to compiler optimization and that it is read every time. The ioremap function converts the physical address to a virtual address, mapping the IO port registers into ordinary memory units for access.
Unmapping: void iounmap(void* addr) // Cancel the IO address mapped by ioremap. For example: iounmap(GPFSEL0); iounmap(GPSET0); iounmap(GPCLR0); // Release address mapping when unloading the driver
Raspberry Pi IO Port 4 Driver Code
#include <linux/fs.h> //file_operations declaration #include <linux/module.h> //module_init module_exit declaration #include <linux/init.h> //__init __exit macro definition declaration #include <linux/device.h> //class device declaration #include <linux/uaccess.h> //copy_from_user header file #include <linux/types.h> //device number dev_t type declaration #include <asm/io.h> //ioremap iounmap header file
static struct class *pin4_class;
static struct device *pin4_class_dev;
static dev_t devno; //device number
static int major =231; //major device number
static int minor =0; //minor device number
static char *module_name="pin4"; //module name
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL; //these three lines set the register addresses //the volatile keyword ensures that this instruction will not be omitted due to compiler optimization and that it is read every time
//led_open function
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //kernel print function, similar to printf
//configure pin4 as output pin
*GPFSEL0 &=~(0x6 <<12); // set bit13, bit14 to 0 //0x6 is 110 <<12 left shift 12 bits ~invert &and
*GPFSEL0 |=~(0x1 <<12); //set bit12 to 1 |bitwise or
return 0;
}
//read function
static int pin4_read(struct file *file,char __user *buf,size_t count,loff_t *ppos)
{
printk("pin4_read\n"); //kernel print function, similar to printf
return 0;
}
//led_write function
static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
int usercmd;
printk("pin4_write\n"); //kernel print function, similar to printf
//get the value from the upper layer write function
copy_from_user(&usercmd,buf,count); //read the user input instruction into usercmd
//operate the io port based on the value, high or low
if(usercmd == 1){
printk("set 1\n");
*GPSET0 |= 0x01 << 4;
}
else if(usercmd == 0){
printk("set 0\n");
*GPCLR0 |= 0x01 << 4;
}
else{
printk("undo\n");
}
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
//static limits the scope of this structure to this file.
int __init pin4_drv_init(void) //real driver entry
{
int ret;
devno = MKDEV(major,minor); //create device number
ret = register_chrdev(major, module_name,&pin4_fops); //register driver tell the kernel to add this driver to the kernel driver list
pin4_class=class_create(THIS_MODULE,"myfirstdemo"); //let the code automatically generate devices under dev
pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //create device file
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4);
printk("insmod driver pin4 success\n");
return 0;
}
void __exit pin4_drv_exit(void)
{
iounmap(GPFSEL0);
iounmap(GPSET0);
iounmap(GPCLR0); //release address mapping when unloading the driver
device_destroy(pin4_class,devno);
class_destroy(pin4_class);
unregister_chrdev(major, module_name); //unload driver
}
module_init(pin4_drv_init); //entry, when the kernel loads the driver, this macro will be called to call pin4_drv_init function
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
1. Set the address of the register
Set the address of the register, but this way of writing is problematic. As we mentioned earlier, in the kernel, the code and the upper-level code access are virtual addresses (VA), but now we are setting physical addresses, **== must convert physical addresses to virtual addresses ==**
//these three lines set the register addresses
volatile unsigned int* GPFSEL0 = volatile (unsigned int *)0x3f200000;
volatile unsigned int* GPSET0 = volatile (unsigned int *)0x3f20001C;
volatile unsigned int* GPCLR0 = volatile (unsigned int *)0x3f200028;
//the volatile keyword ensures that this instruction will not be omitted due to compiler optimization and that it is read every time
We first initialize the address:
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
Then assign values in the initialization int __init pin4_drv_init(void) //real driver entry
.
//integer 11 //0xb 11 00010001 even if it is hexadecimal, the left side is
volatile unsigned int* GPFSEL0
, the right side is also forcibly converted to(volatile unsigned int*)
volatile
ensures that this instruction will not be omitted due to compiler optimization and that it is read every time because it is an address I want it to be unsignedunsigned
When we write driver programs, the starting address of the IO space is 0x3f000000
, plus the GPIO offset 0x2000000
, so the physical address of GPIO should start from 0x3f200000
.
Then, based on this, perform Linux system MMU memory virtualization management, mapping to virtual addresses. This uses a function ioremap
//convert physical address to virtual address, map IO port registers into ordinary memory units for access
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4);
GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4);
GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4); //4 is 4 bytes
2. Configure pin4 as output pin
Configure pin4 as output pin bit 12-14 set to 001
31 30 ······14 13 12 11 10 9 8 7 6 5 4 3 2 1
0 0 ······0 0 1 0 0 0 0 0 0 0 0 0 0 0
//configure pin4 as output pin bit 12-14 set to 001
*GPFSEL0 &=~(0x6 <<12); // set bit13, bit14 to 0 //0x6 is 110 <<12 left shift 12 bits ~invert &and
*GPFSEL0 |=~(0x1 <<12); //set bit12 to 1 |bitwise or
Forget to use bitwise AND, bitwise OR, click here.
3. Get the value from the upper layer write function, operate the IO port based on the value, high or low
Use copy_form_user(char *buf , user_buf , count)
to get the value from the upper layer write function.
int usercmd;
copy_from_user(&usercmd,buf,count); //read the user input instruction into usercmd
//operate the IO port based on the value, high or low
printk("get value\n");
if(usercmd == 1){
printk("set 1\n"); //set to 1
*GPSET0 |= 0x01 << 4; //using | or operation to not affect other bits
//write 1 to enable register to set bit4 to high
}
else if(usercmd == 0){
printk("set 0\n"); //clear to 0
*GPCLR0 |= 0x01 << 4; //using | or operation to not affect other bits
//write 1 to clear register to set bit4 to low
}
else{
printk("undo\n"); //prompt unsupported instruction
}
4. Unmapping
Unmapping: void iounmap(void* addr);
//cancel the IO address mapped by ioremap.
void __exit pin4_drv_exit(void)
{
iounmap(GPFSEL0); //unmap GPFSEL0
iounmap(GPSET0); //unmap GPSET0
iounmap(GPCLR0); //unmap GPCLR0
device_destroy(pin4_class,devno);//first destroy the device
class_destroy(pin4_class);//then destroy the class
unregister_chrdev(major, module_name); //unload driver
}
Upper-level test code:
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>
int main()
{
int fd;
int cmd;
int data;
fd = open("/dev/pin4",O_RDWR);
if(fd<0){
printf("open failed\n");
}else{
printf("open success\n");
}
printf("input commnd:1/0 \n 1:set pin4 high \n 0 :set pin4 low\n");
scanf("%d",&cmd);
printf("cmd = %d\n",cmd);
fd = write(fd, &cmd,4); //cmd type is int, so write 4
}
Driver Unloading
After installing the driver, you can use the command: sudo rmmod + driver name
(no need to write ko) to unload the driver.
IO Port Driver Code Compilation
-
First, in the system directory /SYSTEM/linux-rpi-4.14.y
, use the command:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make modules
to compile the driver module and generate the.ko
file. -
Then send the compiled driver to the Raspberry Pi: scp ./drivers/char/pin4driver.ko [email protected]:/home/pi
, then compile the upper-level code:arm-linux-gnueabihf-gcc pin4test.c -o realtest
, and then send the test code to the Raspberry Pi:scp realtest [email protected]:/home/pi/
-
Then on the Raspberry Pi, use the command: insmod pin4drive.ko
to load the driver (thenlsmod
to see this driver), -
Then use the command: sudo chmod 666 /dev/pin4
to give pin4 this device access permissions, you can also use mk5sum to check the value of the driver file on the virtual machine and check the value of the driver file on the Raspberry Pi to see if it is consistent. -
dmesg
to view kernel print information, as shown in the figure below: -
Then run the test code, in a new window, use the command gpio readall
to see that theBCM
under4
pin mode is output mode, and the level is low or high (depending on the input from the upper-level code, input 0 is low, input 1 is high). Here I input 0, as shown in the figure below:
About the GPIO Address Issue in the Driver Code
About the GPIO address issue in the driver code:
-
7Ennnnn means from 7E00000 to 7EFFFFFF, F2000000 is the virtual address mapped to 3F000000, then 7E00000 corresponds to F200000, and the chip manual uses the address that corresponds to the virtual address F200000—7E00000, the offset of the address in the chip manual is the same as the offset of the physical address. -
According to the description in the above image, the physical address range of the peripheral is from 0x3F000000 to 0x3FFFFFFF, so the actual physical address corresponding to the 7E200000 you see should be 0x3F000000 + (7E200000-7E000000). -
GPFSEL0=(volatile unsigned int *)ioremap(0x3f200000,4); GPSET0 =(volatile unsigned int *)ioremap(0x3f20001C,4); GPCLR0 =(volatile unsigned int *)ioremap(0x3f200028,4); -
0x3f200000, 0x3f20001C, 0x3f200028 are physical addresses, the starting address of the peripheral space of the Raspberry Pi is 0x3f000000. According to the chip manual, the corresponding register offset is, for example, the actual address of the GPFSEL0 register is 0x3f200000=0x3F000000 + (7E200000-7E000000). -
Then, through the data manual, you can see the bus address of the relevant registers of the Raspberry Pi, and the mapping relationship with the virtual address can be obtained, as shown in the figure below: