1 00:00:00,270 --> 00:00:01,190 Hello, 2 00:00:01,690 --> 00:00:08,280 in this lecture, we will create a new file to process the interrupts. Before we delve into the details, 3 00:00:08,280 --> 00:00:10,230 we need to discuss a little bit about calling conventions. 4 00:00:10,830 --> 00:00:17,040 In this course, we use system v amd64 calling convention where the first six arguments are stored 5 00:00:17,040 --> 00:00:23,220 in rdi rsi rdx rcx r8 and r9. 6 00:00:23,670 --> 00:00:26,820 Other parameters are passed on the stack in reverse order. 7 00:00:27,720 --> 00:00:30,480 The return value is stored in rax register. 8 00:00:31,840 --> 00:00:40,420 rax, rcx, rdx, rsi and so on are called caller saved registers 9 00:00:40,420 --> 00:00:46,240 which means the caller has to save the value of these registers if it calls other function which could alter these registers. 10 00:00:46,630 --> 00:00:52,030 Normally the caller will save the value on the stack before it calls other functions 11 00:00:52,030 --> 00:00:54,700 and restore the value of the registers after the functions return. 12 00:00:56,160 --> 00:01:04,050 Other registers, such as rbx, rbp, r12, etc, are called callee saved registers 13 00:01:04,050 --> 00:01:08,490 which means the value of these registers are preserved when we call a function and return from it. 14 00:01:09,450 --> 00:01:10,750 OK, let's get started. 15 00:01:13,070 --> 00:01:18,770 Because we need the interrupt structures when we deal with the interrupts in c file. We created a header file 16 00:01:18,770 --> 00:01:22,250 trap.h to define these structures. 17 00:01:24,810 --> 00:01:30,750 in the header file, the first thing we are going to do is add a guard so that the header file is included only once. 18 00:01:30,750 --> 00:01:37,860 What it does is that if this label is not defined then define it and the next time the file gets included, 19 00:01:37,860 --> 00:01:43,200 the whole block of code will be skipped beacuse the label is defined. 20 00:01:47,570 --> 00:01:53,660 Next we include stdint file because we will define the structures using fixed width data types. 21 00:01:54,850 --> 00:02:01,930 The first structure we define is idt entry. As we have learned in the previous lectures, 22 00:02:02,080 --> 00:02:05,970 the first two bytes stores lower 16 bits of offset. 23 00:02:06,790 --> 00:02:10,630 So the data type we use is unsigned integer which is 16 bits. 24 00:02:12,150 --> 00:02:14,470 The selector is stored in the next two bytes. 25 00:02:14,850 --> 00:02:22,170 We use the same data type. Then we have reserved field and attribute, both of which are 8bits. 26 00:02:23,590 --> 00:02:29,950 next two fields are the mid and upper bits of the offset. one is 16bits and another is 32bits. 27 00:02:30,880 --> 00:02:32,660 The last 32bits are reserved. 28 00:02:33,370 --> 00:02:39,060 Ok the next structure is idt pointer which is used for load idt instruction. 29 00:02:39,940 --> 00:02:44,440 The first two bytes are the limit and the next 8 bytes are the address of the IDT. 30 00:02:45,400 --> 00:02:52,510 Note that here we add the option attribute packed so that the structure is stored without padding in it. 31 00:02:52,510 --> 00:02:57,880 Otherwise the data is stored to the natural alignment and we will have padding between these two items in this case 32 00:02:57,880 --> 00:02:58,320 . 33 00:02:58,810 --> 00:03:01,220 As a result, the load idt instruction 34 00:03:01,240 --> 00:03:06,280 will load the register with wrong data. So be sure to add the packed attribute. 35 00:03:07,680 --> 00:03:12,720 Ok, before we finish this file, we need another assembly file also called trap. 36 00:03:17,140 --> 00:03:22,360 Remember we have removed all the interrupt related code in the kernel file in the last video. 37 00:03:22,810 --> 00:03:25,840 They will be added in this file. As you can see, 38 00:03:25,870 --> 00:03:30,340 we declare those handlers globally so that we can reference them in the c file. 39 00:03:30,970 --> 00:03:36,670 The vector0 handles divide by zero exception, vector1 is for debug exception and so on. 40 00:03:37,850 --> 00:03:44,660 Some of vectors are reserved such as vector 9, vector 15. 41 00:03:44,660 --> 00:03:48,320 All these interrupts are processed in the same function called handler in c file. 42 00:03:48,980 --> 00:03:54,470 Since it’s not defined in this file, we use extern which means the function is not defined here 43 00:03:54,470 --> 00:03:55,730 but in other modules. 44 00:03:58,380 --> 00:04:01,050 The end of interrupt, read isr and load idt 45 00:04:01,380 --> 00:04:03,960 are the functions also used in the c file. 46 00:04:05,180 --> 00:04:07,430 OK, we start off with vector0. 47 00:04:08,780 --> 00:04:12,930 We pushed two value on the stack. The first one is error code. 48 00:04:13,340 --> 00:04:19,660 Some of the exceptions such as general fault exception have error code pushed on the stack by the processor before the handler executes. 49 00:04:19,680 --> 00:04:24,300 But divide by zero exception doesn’t have error code, 50 00:04:24,800 --> 00:04:28,640 so we push 0 by ourselves acting as a placeholder. 51 00:04:30,070 --> 00:04:36,610 The second value pushed on the stack is the index value so that we know which exception or interrupt is actually called. 52 00:04:37,030 --> 00:04:41,440 We push 0 which means this is the first exception and jump to trap. 53 00:04:42,400 --> 00:04:47,340 The reason we push them on the stack is that we will handler all the exceptions and interrupts 54 00:04:47,340 --> 00:04:51,420 in the same function in c file. So we need the data to know the details. 55 00:04:52,140 --> 00:04:53,120 OK, let's move on. 56 00:04:55,650 --> 00:04:57,870 The following exceptions are pretty much the same. 57 00:04:59,150 --> 00:05:02,150 We push index 1 on the stack in the second exception, 58 00:05:03,830 --> 00:05:06,590 the third one, the fourth one and so on. 59 00:05:10,430 --> 00:05:16,320 In the vector8, we don’t push the error code since the CPU pushes the error code on the stack when the exception generates. 60 00:05:16,340 --> 00:05:23,060 So we only push the index number 8 in this case and jump to trap. 61 00:05:24,180 --> 00:05:27,780 The vector9 is reserved, we move to the next exception. 62 00:05:28,820 --> 00:05:30,650 Vector10 through 14 63 00:05:31,750 --> 00:05:34,690 have error codes pushed on the stack by the processor. 64 00:05:35,810 --> 00:05:38,060 Vector15 is reserved 65 00:05:38,570 --> 00:05:44,270 We move on. Vector20 trough 31 are also reserved. In our system, 66 00:05:44,280 --> 00:05:45,950 we don’t handler these exceptions. 67 00:05:46,340 --> 00:05:49,070 If the exception occurs, we just stop the system. 68 00:05:49,610 --> 00:05:51,440 We will see how to do it in a minute. 69 00:05:52,880 --> 00:05:55,040 Then we get to the hardware interrupt handlers. 70 00:05:55,040 --> 00:05:59,450 Remember the timer interrupt is assigned with vector32. 71 00:06:00,260 --> 00:06:03,470 So we push the index 32 and jump to trap. 72 00:06:04,010 --> 00:06:07,730 The spurious interrupt is assigned with vector39. 73 00:06:09,370 --> 00:06:15,580 The process for all the exceptions and interrupts is the same, that is, push error code and index number 74 00:06:15,580 --> 00:06:16,630 then jump to trap. 75 00:06:17,540 --> 00:06:19,130 So let's see the label Trap. 76 00:06:23,210 --> 00:06:29,030 The trap procedure is really simple. We save the state of CPU by pushing the general purpose registers. 77 00:06:30,090 --> 00:06:34,650 Then we increment the character in order to see it changing on the screen. 78 00:06:35,820 --> 00:06:41,970 Then we call handler function in c file. Remember in system v calling convention, 79 00:06:41,970 --> 00:06:44,040 the first argument is passed in register rdi. 80 00:06:44,460 --> 00:06:47,130 What it does is passing the stack pointer to handler. 81 00:06:48,220 --> 00:06:54,190 When we return from it, we reach the end of trap, restore the general purpose registers and return. 82 00:06:55,760 --> 00:06:56,290 Note that 83 00:06:56,300 --> 00:07:01,130 before we return, we need to adjust rsp register to make it point to the correct location. 84 00:07:03,200 --> 00:07:10,520 As you see, we have pushed two value which is 16 bytes on the stack before we jump to trap. So we add rsp,16 85 00:07:10,520 --> 00:07:16,400 to make the rsp point to the original location when the exception or interrupt gets called by the processor. 86 00:07:18,420 --> 00:07:20,820 If the error code is pushed by the processor 87 00:07:22,830 --> 00:07:27,620 like vector 8, we still need to add 8 bytes manually to skip the error code. 88 00:07:28,620 --> 00:07:31,740 So here we simply add 16 in the trap return. 89 00:07:33,200 --> 00:07:34,550 There are other procedures, 90 00:07:37,980 --> 00:07:43,470 such as end of interrupt. As we have seen it before, it send the eoi command to the PIC. 91 00:07:43,470 --> 00:07:49,170 Read isr. Processing spurious interrupt requires reading ISR, 92 00:07:49,260 --> 00:07:50,490 So we add it here. 93 00:07:51,560 --> 00:07:53,570 The last procedure is load idt. 94 00:07:53,870 --> 00:08:00,770 The reason we need this procedure is that we cannot load idt with load idt instruction directly in c. 95 00:08:00,770 --> 00:08:03,380 Instead, we define a procedure load idt 96 00:08:03,390 --> 00:08:05,990 and call this procedure in c file to load idt. 97 00:08:07,310 --> 00:08:10,770 This procedure has one parameter which is the address of idt. 98 00:08:11,210 --> 00:08:14,030 So the parameter is stored in register rdi. 99 00:08:14,960 --> 00:08:19,550 Ok that’s it for trap.asm. Back to the header file. 100 00:08:21,890 --> 00:08:27,470 Since the interrupt handler is defined in the assembly file, we need to declare them in the header file. 101 00:08:28,730 --> 00:08:31,640 As you see, they have no return value and parameters. 102 00:08:33,770 --> 00:08:39,620 Load idt takes one parameter idt pointer. Read isr return a 8-bit value. 103 00:08:40,770 --> 00:08:41,169 Alright, 104 00:08:41,190 --> 00:08:45,680 we look at another file trap.c which sets up the idt. 105 00:08:47,910 --> 00:08:52,540 To use the structures we just defined in the header file, we include trap.h. 106 00:08:54,090 --> 00:09:02,320 We define global variables called idt pointer and vector array which includes 256 items. 107 00:09:02,340 --> 00:09:08,640 Idt pointer is used when we call load idt function. And vector array is actually the new IDT 108 00:09:08,640 --> 00:09:10,980 which has 256 entries in it. 109 00:09:11,990 --> 00:09:18,080 We also use keyword static so that they are visible only in this file, since we don’t reference them in other files 110 00:09:18,080 --> 00:09:18,980 . 111 00:09:19,880 --> 00:09:21,320 OK, let's see the functions. 112 00:09:23,080 --> 00:09:26,620 The initialize idt entry function takes three parameters, 113 00:09:28,170 --> 00:09:34,430 the address of idt entry, the address of handler which we defined in assembly file and the attribute of the idt entry. 114 00:09:34,430 --> 00:09:41,330 It has no return value and it’s a static function which means this function is only visible in this file 115 00:09:41,420 --> 00:09:42,080 . 116 00:09:42,920 --> 00:09:46,450 In the function, we assign the value to each field of the entry. 117 00:09:47,240 --> 00:09:52,120 We copy the lower 16bits of the address to the field low. The selector is 8 118 00:09:52,120 --> 00:09:54,560 which is the kernel code segment selector. 119 00:09:55,040 --> 00:09:58,450 Then copy the attribute and the mid, high parts of the address. 120 00:09:59,420 --> 00:10:02,510 This part is shift right the address 16bits 121 00:10:03,660 --> 00:10:06,240 and this shifts right the address 32bits. 122 00:10:07,480 --> 00:10:08,570 OK, let's continue. 123 00:10:09,970 --> 00:10:15,790 In function initialize idt we initialize idt entry by passing the address of idt entry, 124 00:10:16,180 --> 00:10:23,530 the handler we want to set in the entry and the attribute 8e, the meaning of which is discussed in the last section. 125 00:10:24,950 --> 00:10:27,320 These idt entries are set the same way. 126 00:10:30,260 --> 00:10:33,530 one thing left to do is load the idt pointer. 127 00:10:34,760 --> 00:10:42,680 Copy the limit of the idt which is the length of idt minus 1 and the address of idt. The we load the idt 128 00:10:42,680 --> 00:10:43,900 by calling the function load idt 129 00:10:44,360 --> 00:10:45,110 and we are done. 130 00:10:47,220 --> 00:10:53,070 The last one is handler where all the interrupts are processed here. 131 00:10:53,070 --> 00:10:57,660 The handler has no return value and it only takes one parameter, we call it the trap frame. 132 00:10:58,320 --> 00:11:02,850 The parameter is actually the stack pointer as we have seen it in the assembly file. 133 00:11:03,570 --> 00:11:05,760 Now we define this structure in the header file. 134 00:11:11,460 --> 00:11:17,190 When the handler is called, the state of the stack is like this, the top of the stack is pointing to 135 00:11:17,370 --> 00:11:18,290 the value of r15, 136 00:11:19,010 --> 00:11:20,930 as you see, the general purpose registers, 137 00:11:22,060 --> 00:11:29,590 the index number and some of the error code are pushed manually in the assembly file, the rip rsp rflag value, etc 138 00:11:29,590 --> 00:11:36,370 are pushed by the processor. So we define the trap frame according to it, 139 00:11:36,410 --> 00:11:37,660 each data here is 8 bytes. 140 00:11:38,700 --> 00:11:43,980 Ok at this point, we can access the data on the stack by referencing the item in this structure. 141 00:11:45,690 --> 00:11:51,600 In the handler, we check the index which we pushed on the stack to see which interrupt or exception actually occurs 142 00:11:51,600 --> 00:11:52,140 . 143 00:11:52,950 --> 00:11:55,260 Switch statement is used to do that. 144 00:11:56,240 --> 00:12:01,190 Here we only deal with two interrupts that is the timer and spurious interrupt. 145 00:12:02,910 --> 00:12:09,120 If it is the timer interrupt, the index number we pushed on the stack is 32. We simply send 146 00:12:09,120 --> 00:12:10,500 eoi and return. 147 00:12:12,800 --> 00:12:18,470 If it is the vector 39, we will read isr to see if it is a real interrupt. 148 00:12:19,450 --> 00:12:25,540 We check bit 7 of the return value. If it is set, then it is real interrupt, 149 00:12:25,540 --> 00:12:26,740 we send eoi and return. 150 00:12:27,310 --> 00:12:30,420 If it’s 0, then it is a spurious interrupt, 151 00:12:30,460 --> 00:12:31,600 we simply return. 152 00:12:33,910 --> 00:12:39,440 As for other exceptions and interrupts, we will stop the system because there should be some errors in our kernel 153 00:12:39,730 --> 00:12:40,180 . 154 00:12:41,800 --> 00:12:47,260 The last thing to do before we finish the trap file is add the declaration of init idt entry in the header file, 155 00:12:47,260 --> 00:12:50,930 because we will call the function in main function. 156 00:12:51,850 --> 00:12:52,340 Alright, 157 00:12:52,360 --> 00:12:53,530 the trap file is done. 158 00:12:54,870 --> 00:13:02,160 In the main.c file, we call the init idt function to initialize the idt. Since it is defined in the trap file, 159 00:13:02,380 --> 00:13:06,600 we need to add the header file which includes the declaration of the function. 160 00:13:08,310 --> 00:13:13,230 In the last video, we have changed the kernel file. So we open kernel.asm. 161 00:13:18,120 --> 00:13:24,810 As you see, the interrupt is not enabled. We should enable interrupt, otherwise we will not receive timer interrupt. 162 00:13:24,810 --> 00:13:29,430 So we enable interrupt after the main function returns. 163 00:13:31,750 --> 00:13:37,150 In this system, handlers are supposed to be working after we get to ring3. For the moment 164 00:13:37,180 --> 00:13:40,150 we are still in kernel mode and we want to test timer handler now. 165 00:13:40,510 --> 00:13:45,970 Remember we didn't set up ss register after we enter 64-bit mode. 166 00:13:47,100 --> 00:13:53,660 So we need to load ss segment selector, otherwise the invalid ss selector could be loaded when the handler returns. 167 00:13:54,080 --> 00:13:58,190 In ring0, we can simply load a null descriptor to ss register. 168 00:14:03,410 --> 00:14:05,250 OK, let's edit the build script. 169 00:14:05,390 --> 00:14:09,460 We added two files, trap assembly file and trap c file. 170 00:14:10,070 --> 00:14:15,050 The ouput file of the assembly code is called trapa.o and the output file of c file 171 00:14:15,050 --> 00:14:19,510 is called trap.o. 172 00:14:19,520 --> 00:14:22,460 In the ld command, we added trap.o and trapa.o. 173 00:14:23,240 --> 00:14:24,730 With all the files prepared, 174 00:14:24,740 --> 00:14:25,970 let’s build the project. 175 00:14:27,560 --> 00:14:30,590 OK, we open the bochs to test it out. 176 00:14:32,620 --> 00:14:34,810 You can see character is changing on screen.