I believe you must have read my last article (in short, only IO APIC can accept interrupts from PCI). Yes, as long as you write the interrupt driver this time, you can officially start the development of PCI!
The performance of APIC is worse than that of 8259A interrupt processor. Without comparing the actual performance, let's talk about the age when they were born
8259A:
It is designed for 16 bit processors such as 8085A and 8086 / 8088, with an addressing capacity of only 20 bits (about 1MB). Among them, the 8086 processor I can find (the chip CPU with the largest dominant frequency of only 8MB) also came out in 1978. Ah, I know, the time of reform and opening up! Obviously, when the running speed has been amazing, it has almost exited the world stage (of course, most CPUs and motherboards have stubbornly retained it in order to be compatible with the ancient operating system)
Let's look at the birth time of PCI, which was proposed by intel in 1991
Therefore, it is obvious that either the 8259a is completely incompatible with PCI, or it needs to use a complex interrupt routing mechanism to be received by the processor. Even if it can, the transmission performance of interrupts will decline exponentially when a large number of interrupts break out
Therefore, following the trend of the times, APIC(Advanced Programmable Interrupt Controller) was born! Look, this name sounds very advanced. It's more than pic, and an A becomes A lot more advanced. Next, let's look at the birth age of APIC:
Since the Pentium P54c in 1994, Intel has built the native APIC in their processors. (from Baidu Encyclopedia)
Although I don't know why I turned local into local instead of local, I'm sure it was launched 16 years later than 8259A! This is a great leap forward for the interrupt controller
Therefore, to sum up, modern operating systems rarely use the old Interrupt Controller 8259a
So, let's start the development of our apic driver!
Like the drivers of all devices, one thing must be done at the beginning of operation, that is, to detect whether the computer has this device. How to detect it? It's simple. Just use the CPUID instruction to detect it. So I wrote a new lib. This lib is added to / Lib in the development folder, and its name is libasys A (the names of libraries or header files not officially certified or officially released contain capital letters!) The function / include.sysinclude contained in / Aslib H, here I simply encapsulate the assembly instruction of CPUID and use (register) for the returned parameters_ Save. Although there is no 32-bit register, you can read the internal value directly with 64 bit register. The following is cpuid_c source code
;From./lib/reg_op.asm cpuid_c: ;cpuid push rax push rbx push rcx push rdx mov rax,rdi cpuid mov [rel rax_],rax mov [rel rbx_],rbx mov [rel rcx_],rcx mov [rel rdx_],rdx pop rdx pop rcx pop rbx pop rax ret
Then we can use it to read cpuid in c language environment. Of course, bts, btr and bt are also packaged into c language instructions. The prototype is as follows
;./libs/bitoperate.asm [bits 64] test_bit: bt rdi,rsi jc bt_carry mov rax,0 ret bt_carry: mov rax,1 ret set_bit: bts rdi,rsi mov rax,rdi ret reset_bit: btr rdi,rsi mov rax,rdi ret
With these functions, we can start the detection of apic(x) and x2apic. The detection functions are as follows
bool init_apic(void) { cpuid_c(0x80000001); state_printk(PRINT_OK,"CPUID 0x80000001 returns ECX 0x%X EDX 0x%X\n",rcx_,rdx_); if(test_bit(rdx_,9)) { state_printk(PRINT_OK,"APIC Support --- YES\n"); if(test_bit(rcx_,21)) { state_printk(PRINT_OK,"x2APIC Support --- YES\n"); } else { //Several lines are omitted here } return true; } else state_printk(PRINT_ERR,"APIC Support --- NO\n"); return false; }
But just as I happily put it on the virtual machine, it told me
What? No version? It's okay. It's nothing. What is a virtual machine? I still have this real machine without experiment.
So with expectation, I restarted the computer, but suddenly
Pawloader displays an error (if it's inconvenient to take photos here, please describe it in words first) and doesn't support APIC. Of course, I don't believe it at this time. Why doesn't a 64 bit LGA775 CPU support APIC? So I transcribed the CPUID 0x1 and 0x20100800, entered ubuntu with suspicion, and then looked at it in the programmer mode of calculator:
APIC is really not supported
***(some civilized terms are shielded here). The clown is actually myself. There are really 64 bit pci enabled devices that do not support APIC
No, no wonder the GT610 on this computer has a normal running score of 6500, but only more than 3000 on my computer. Originally, there was no apic, and the performance was discounted by 50% (even so, it can smoothly play the original God, the original God YYDS under low resolution!), G41 motherboard YYDS!
But it's impossible to design your own system, and your own computer doesn't support it? So I looked through the book again and found some wonderful things
Good guy!
In this way, PCI interrupts can be received by 8259a and will be allocated to irq3, 4, 5, 6, 7, 9, 10, 11, 12, 14 and 15 interrupt lines. For which one to allocate, you only need to read the PCI configuration space. Here is the structure diagram of PCI device configuration space
According to my experiments on virtual machines and real machines, I found that interrupt lines are basically numbers like 3, 4, 5, 6, 7, 9, 10, 11, 12, 14 and 15 (excluding the special case of 0x255 on the real machine), so I dare to guess that this thing is the interrupt line number of link 8259a. Oh, this means that we are ready to share the interrupt number! Ah, my hair, if I write such super difficult code a few more times, my hair will be as bald as the chemistry teacher (although my hair is still very thick, there are many bug s in natural programs, oh, no, there are many features). But I found some amazing things on the Internet. The efficiency of polling device drivers for shared interrupts is very touching, but there's no way. Who calls my computer G41 pcie protocol decades ago, and even there's no 3.0 (which means MSI and msi-x can't be used),
8259a also supports PCI, but in the image in the previous chapter, 8259a clearly does not support PCI interrupt, but I followed it. I went online to search PIRQ MAPPING, which was even more exciting
Yes, the table in Tian Yu's book omits the devices sharing interrupt pins, and then according to this article
https://blog.csdn.net/weixin_30300225/article/details/99421416
We can know which pin of pci configuration space actually corresponds to, PIRQABCD And other pins in order from irq3,4,5,6,7,9,10,11,12,14,15 in the table, absolutely, really absolutely, but during the startup of bios in my computer, I saw that bios listed all pci devices and allocated IRQ, that is, we only need to set pci intr in pci configuration space_ Read the value of line and you will know which pirq it is
Of course, please refer to the following article for the specific link between pci PIRQABCD and irq (there is no guarantee that it will still exist after N years)
In short, this chapter must let PCI devices trigger interrupts
So I went to the Internet for a period of time (the reason for delaying the change appeared...), In a lonely and joking way, I turned to two good babies UHCI Design guide and EHCI Design guide. The two manuals can't be found in the document library of the usb official website. They are found on douding.com and another website, which introduces the operation mechanism of UHCI (i.e. USB1.1), the data structure and the use of IO registers (EHCI is the same). Yes, I'm going to let UHCI trigger interrupts for me today, So I first wrote out the UHCI detection (initialization) program, and the code is as follows
//path = minefunction/PawLoader/driver/usb/uhci.c bool probe_uhci(struct pci_info *dev) { u32 class = in_pci_conf32(dev,PCI_REG_CLASS); //In minefunction / pawloader / driver / PCI / PCI_ In conf.c class >>=8; if(class!=PCI_USB_DEV) return false; //Determine the class value of the device to determine whether it is a USB root hub driver_printk(PRINT_OK,DRIVER_NAME,"Hello UHCI !\n"); //According to the 10th side of UHCI design guid, PCI_ config_ The io base address of uhci is stored in bar4 of space //Note that this value is usually initialized by the bios when the efi is started, but it is uncertain whether it will be assigned an io address after the efi is started u32 tmp_bit_map = in_pci_conf32(dev,PCI_REG_BAR_4_); if(test_bit(tmp_bit_map,0)!=true) return false; //If it does not occupy io resources, it is not the uhci supported by the program tmp_bit_map >>= 4; tmp_bit_map <<= 4; //Wash away its properties if(tmp_bit_map == 0) return false; //Resource allocation for pci devices is not supported for the time being struct hc_device hc_dev; hc_dev.io_base = (u16)tmp_bit_map; //Write down the address of the io register driver_printk(PRINT_OK,DRIVER_NAME,"UHCI IO PORT 0x%x\n",hc_dev.io_base); u16 intr_op = io_in2b(UHCI_REG_OFF_INTR+hc_dev.io_base); //Read interrupt control register into intr_op = set_bit(intr_op,UHCI_INTR_SPIE_BIT); //Whatever the interruption, it's over when one brain starts intr_op = set_bit(intr_op,UHCI_INTR_IOCE_BIT); intr_op = set_bit(intr_op,UHCI_INTR_RIE_BIT); intr_op = set_bit(intr_op,UHCI_INTR_CRCIE_BIT); io_out2b(UHCI_REG_OFF_INTR+hc_dev.io_base,intr_op); //Write back u16 cmd_reg = io_in2b(UHCI_REG_OFF_CMD+hc_dev.io_base); //Similarly, turn on the device cmd_reg = set_bit(cmd_reg,UHCI_CMD_RS_BIT); io_out2b(UHCI_REG_OFF_CMD+hc_dev.io_base,cmd_reg); u16 *debug = (u16 *)0x500; //debug data pointer, which can point to any allocated free memory for(int i=0;i<0x20;i++) debug[i] = io_in2b(hc_dev.io_base+i*2); state_printk(PRINT_OK,"IO DATA : 0x%X 0x%X 0x%X 0x%X\n",debug[0],debug[1],debug[2],debug[3]); state_printk(PRINT_OK,"IO DATA : 0x%X 0x%X 0x%X 0x%X\n",debug[4],debug[5],debug[6],debug[7]); PAUSE; //Run in bochs until it stops. Then use xp/40xw 0x500 to see the contents of uhci io register hc_dev.int_line = in_pci_conf32(dev,PCI_INT_LINE)&0xff; //Write down the interrupt number //Register device register_uhci_dev(&hc_dev); return true; }
There are also some macros in the code, most of which are defined in USB H, you can read by yourself
Of course, in the source code minefunction / USB_ In specific, I bought it for 20 yuan (I don't know whether it's worth it or not). Two documents exist in it, including the link root directory of baidu online disk. You can download it and read it yourself (although it's all in English, Youdao translation is very fragrant)
So I compiled the source code and ran it again. The results in the virtual machine are as follows
Virtual machines are nothing. Look at the real machine, ah, perfect Restart (later found out if it's HCD problem), fantasy (Doctor ecstasy)! What's the matter? Please listen to the next chapter
It's strange, BUG. Oh, no, it's not like my style if the features are not repaired properly (although there are still a lot of features), but it doesn't matter. Anyway, initialize uhci completely first (in the next chapter)
Then, the initialization is all completed, but there is no interruption on the virtual machine, as shown in the figure below
But what can virtual machines represent? What matters is the real machine!
Ah, success! Head!
If the casually written HCD can succeed, I can go to be a half immortal to calculate divination. Take a closer look at the STS in the figure_ Reg: 0x30, what do you mean? The UHCI device hangs and HC detects a fatal error. We need to stop the device to prevent HC from using the wrong information to continue to generate an error interrupt
Ha, I know it's either my program or bochs. This time, it's different. It's all wrong. If the negative is positive, it's "successful". However, it doesn't matter. The wrong interrupt is always better than nothing. It's estimated that it's no problem to write the TD queue header in the next chapter
By the way, hang up the source code link
Link: https://pan.baidu.com/s/1m9WZVL4zFlnsb7t2aL5kng Extraction code: sdpk