1 00:00:00,390 --> 00:00:06,630 In this lecture we will learn how to deal with hardware interrupts. The programmable interrupt controller 2 00:00:06,630 --> 00:00:09,270 is used to manage the interrupt requests. 3 00:00:09,520 --> 00:00:13,800 So first we should initialize the PIC before we can handle the hardware interrupts. 4 00:00:13,800 --> 00:00:15,740 In this video, 5 00:00:15,760 --> 00:00:18,180 we will set up the timer as an example. 6 00:00:18,660 --> 00:00:24,660 In order to do that, we also need to initialize the programmable interval timer which will fire the interrupt periodically 7 00:00:24,660 --> 00:00:26,130 . 8 00:00:26,670 --> 00:00:29,340 We start off by talking about the interrupt controller. 9 00:00:31,510 --> 00:00:38,960 As you can see, there are two chips linked together. Each chip has 8 IRQ signals. 10 00:00:38,980 --> 00:00:41,740 The slave is linked to the master via IRQ2. 11 00:00:42,160 --> 00:00:45,190 So actually we have a total of 15 interrupts. 12 00:00:46,610 --> 00:00:53,840 The programmable interval timer, for example, uses IRQ0 of the master chip. The keyboard uses 13 00:00:53,840 --> 00:00:57,100 IRQ 1 of the master chip, etc. 14 00:00:58,120 --> 00:01:00,370 When we press a key, 15 00:01:00,390 --> 00:01:05,890 the keyboard will send a signal to the interrupt controller and the controller will then send signals to the processor 16 00:01:05,890 --> 00:01:09,520 according to the settings we write to the controller. 17 00:01:10,390 --> 00:01:16,810 The processor finds the handler for that interrupt by the vector number and call that handler which we have seen 18 00:01:16,810 --> 00:01:17,980 in the previous lectures. 19 00:01:19,420 --> 00:01:26,500 There are three registers in each chip. These registers are used to service the interrupts. 20 00:01:26,650 --> 00:01:30,910 They are 8-bit registers with each bit representing the corresponding IRQ. 21 00:01:32,350 --> 00:01:38,480 When a device, keyboard for example, sends a signal to the chip, the corresponding bit 22 00:01:38,590 --> 00:01:41,170 in Interrupt request register IRR will be set. 23 00:01:42,220 --> 00:01:47,170 If the corresponding bit in the interrupt mask register IMR is 0 24 00:01:48,900 --> 00:01:54,480 meaning that the IRQ is not masked, the PIC will send the interrupt to the processor 25 00:01:55,690 --> 00:02:00,460 and set the corresponding bit in the in-service register ISR. 26 00:02:01,480 --> 00:02:07,180 Generally, we could have multiple IRQs come at the same which means more than one bit in the IRR 27 00:02:07,180 --> 00:02:08,590 will be set. 28 00:02:09,620 --> 00:02:16,940 The PIC will choose which one should be processed first according to the priority. Normally, the IRQ0 29 00:02:16,940 --> 00:02:21,440 has the highest priority and IRQ7 the lowest priority. 30 00:02:22,010 --> 00:02:25,120 And note that the slave is attached to the master via IRQ2. 31 00:02:25,130 --> 00:02:33,320 So, all the IRQs on the slave have higher priority than IRQ3 of the master. 32 00:02:34,270 --> 00:02:36,040 OK, let's open the project. 33 00:02:38,150 --> 00:02:39,590 In the kernel entry, 34 00:02:40,670 --> 00:02:47,930 we initialize the PIT and PIC which requires several steps. First we look at the PIT. 35 00:02:48,470 --> 00:02:53,290 There are three channels in the PIT, through channel 0 to channel 2. 36 00:02:54,400 --> 00:03:00,910 Channel 1 and 2 may not exist and we don’t use them in our system. So we only talk about channel 0 37 00:03:00,910 --> 00:03:01,300 . 38 00:03:01,750 --> 00:03:08,950 The PIT has four registers, one mode command register and three data registers 39 00:03:08,950 --> 00:03:10,260 for the three channels respectively. 40 00:03:11,990 --> 00:03:18,950 We set the command and data registers to make the PIT works as we expect, that is 41 00:03:18,950 --> 00:03:23,300 fire an interrupt periodically. The mode command register has four parts in it. 42 00:03:23,300 --> 00:03:26,120 The bit 0 indicates that 43 00:03:26,150 --> 00:03:27,530 the value PIT uses 44 00:03:27,540 --> 00:03:30,950 is in binary or BCD forms. 45 00:03:31,490 --> 00:03:37,720 We set it to 0 which means in binary form. Bit 1 through 3 is operating mode. 46 00:03:38,030 --> 00:03:45,350 There are several operating modes we can select. In this example, we set to 010 which is mode 2 47 00:03:45,350 --> 00:03:52,540 rate generator used for reoccurring interrupt. Bit 4 to 5 is Access mode. 48 00:03:53,090 --> 00:03:54,590 The data registers are 8 bits. 49 00:03:55,160 --> 00:04:02,870 If we want to write 16-bit value to the PIT, we need to write two bytes in a row. And access mode specifies 50 00:04:02,870 --> 00:04:08,630 in which order the data will be written to the data register, such as low byte only, high byte only, 51 00:04:08,630 --> 00:04:09,740 etc. 52 00:04:10,610 --> 00:04:17,360 in this example, we set the access mode to 11 which means we want to write the low byte first 53 00:04:17,360 --> 00:04:18,560 and then the high byte. 54 00:04:19,760 --> 00:04:26,330 The last part is for selecting the channel with 0 being channel 0, 1 being channel 1 and so on. 55 00:04:26,330 --> 00:04:30,760 Since we only use channel 0, we simply set it to 0. 56 00:04:31,160 --> 00:04:33,470 We move the value to al register. 57 00:04:35,180 --> 00:04:36,530 So we define the label 58 00:04:39,140 --> 00:04:40,780 init pit 59 00:04:43,370 --> 00:04:44,570 We move al 60 00:04:45,900 --> 00:04:46,620 and the value 61 00:04:50,490 --> 00:04:57,280 The address of mode command register is 43. We use out instruction to write the value in al to the register 62 00:04:57,390 --> 00:04:58,230 . 63 00:04:59,370 --> 00:05:02,280 We out 43 64 00:05:03,550 --> 00:05:04,030 al. 65 00:05:05,820 --> 00:05:13,090 Now we can write the settings to data register to make the PIT fire the interrupt as we expect. 66 00:05:13,090 --> 00:05:19,020 In fact, the value we want to write to the PIT is an interval value which specifies when the interrupt is fired 67 00:05:19,020 --> 00:05:19,790 . 68 00:05:20,940 --> 00:05:27,480 The PIT works simply like this, we load a counter value and PIT will decrement this value at a rate of 69 00:05:27,480 --> 00:05:34,260 about 1.2 mega HZ which means it will decrement the value roughly 1.2 million times per second 70 00:05:34,260 --> 00:05:36,840 . 71 00:05:36,850 --> 00:05:41,660 In our system, we want the interrupt fired at 100 HZ which is 100 times per second. 72 00:05:42,690 --> 00:05:44,660 So let's do a simple calculation. 73 00:05:45,060 --> 00:05:48,270 we divide the 1.2 million by 100, 74 00:05:48,720 --> 00:05:53,330 the result is 11 931 which is the count value 75 00:05:53,340 --> 00:05:59,060 we want to write to the register. Note that the counter value is 16-bit value. 76 00:05:59,490 --> 00:06:03,300 So we write the low byte of the value and then write the high byte. 77 00:06:04,280 --> 00:06:10,730 OK, we're move ax 11931 78 00:06:12,090 --> 00:06:18,840 The address of data register of channel 0 is 40. We out 40 79 00:06:20,310 --> 00:06:20,820 al. 80 00:06:23,080 --> 00:06:23,890 Then we move 81 00:06:25,220 --> 00:06:26,400 al ah 82 00:06:28,000 --> 00:06:31,840 And now al holds the high byte of the value. we out 83 00:06:32,750 --> 00:06:33,290 40 84 00:06:34,500 --> 00:06:36,890 al. 85 00:06:37,260 --> 00:06:43,140 Ok the PIT is now running. The next thing we are going to do is we are going to 86 00:06:43,140 --> 00:06:44,430 set up the interrupt controller. 87 00:06:45,450 --> 00:06:51,780 The PIC also has command register and data register. Each chip has its own register set. 88 00:06:53,390 --> 00:07:00,310 The address for the command register of the master chip is 20 and the address of the slave is a0. 89 00:07:00,590 --> 00:07:02,960 So the bit 0 and bit 4 is 1. 90 00:07:03,910 --> 00:07:10,100 The bit 4 means that this is the initialization command followed by another three initialization command words we are about to write. 91 00:07:10,100 --> 00:07:16,540 The bit 0 indicate that we use the last initialization command word 92 00:07:16,540 --> 00:07:16,900 . 93 00:07:18,280 --> 00:07:24,010 OK, we defines a label init pic 94 00:07:27,550 --> 00:07:29,200 We move al 95 00:07:30,670 --> 00:07:31,140 11. 96 00:07:34,480 --> 00:07:37,990 and write it to the command registers using out instruction. 97 00:07:42,010 --> 00:07:45,610 The address of the slave is a0. 98 00:07:47,900 --> 00:07:54,200 Next we write these three command words. The first one specifies the starting vector number of the first IRQ. 99 00:07:54,200 --> 00:08:02,060 Remember the processor has defined the first 32 vectors for its own use. So we can define the vectors number 100 00:08:02,070 --> 00:08:06,050 from 32 to 255. 101 00:08:07,530 --> 00:08:14,430 Here we simply specify the starting vector number of the first IRQ is 32. So we move 102 00:08:17,100 --> 00:08:18,330 al,32. 103 00:08:19,840 --> 00:08:25,990 Instead of writing the data to command register, we write it to the data register, 104 00:08:25,990 --> 00:08:30,180 the address of which is 21 for the master and a1 for the slave. 105 00:08:30,490 --> 00:08:31,570 So we write 106 00:08:31,960 --> 00:08:35,020 the value to 21 107 00:08:37,400 --> 00:08:44,990 As for the slave, we simply place the starting vector number right after the master. 108 00:08:45,000 --> 00:08:50,810 Each chip has 8 IRQs and the first vector number of the master is 32, the second vector is 33 and so on. 109 00:08:51,000 --> 00:08:55,170 So the starting vector number for the slave is 40 in this case. 110 00:08:56,890 --> 00:08:59,050 So we move al 40. 111 00:09:01,410 --> 00:09:03,510 and write it to a1. 112 00:09:06,730 --> 00:09:14,550 The next command word indicate that which IRQ is used for connecting the two PIC chips. 113 00:09:14,560 --> 00:09:22,260 On a regular system, the slave is attached to the master via IRQ 2 as we saw in the slide. If the bit 2 of the word is set, 114 00:09:22,510 --> 00:09:24,760 it means that the IRQ2 is used. 115 00:09:24,940 --> 00:09:28,390 So we write the value 4 to the data register. 116 00:09:30,790 --> 00:09:32,050 we move al 4 117 00:09:33,530 --> 00:09:36,320 And write it to the data register. 118 00:09:38,330 --> 00:09:42,020 The word for the slave meaning the identification which should be 2. 119 00:09:43,230 --> 00:09:45,540 So we write 2 to the register. 120 00:09:50,430 --> 00:09:57,480 The last command word is used for selecting mode. The bit 0 should be 1 meaning that x86 system is used. 121 00:09:57,480 --> 00:10:01,680 The bit 1 is automatic end of interrupt. We don’t use it 122 00:10:01,680 --> 00:10:09,060 and simply set it to 0. The bit 2 to 3 are set to 0 which means buffered mode is not used. 123 00:10:09,330 --> 00:10:14,820 The bit 4 specify the fully nested mode. We don’t use this mode either, and set it to 0. 124 00:10:15,900 --> 00:10:17,030 OK, we write 125 00:10:17,430 --> 00:10:20,130 1 to the register. 126 00:10:28,120 --> 00:10:34,240 Right now the interrupt controller is working. But we have one more thing to do. 127 00:10:34,240 --> 00:10:38,680 Since we have a total of 15 IRQs in the PIC, we only set up one device, 128 00:10:39,040 --> 00:10:39,190 the PIT. 129 00:10:40,920 --> 00:10:49,120 Therefore, we mask all the IRQs except the IRQ0 of the master which PIT uses. Masking an interrupt is simple 130 00:10:49,170 --> 00:10:49,650 . 131 00:10:49,690 --> 00:10:55,350 All we need to do is set the corresponding bit of the IRQ and write the value to the data register. 132 00:10:56,850 --> 00:11:01,320 So here we set the bits 1 through 7 133 00:11:04,520 --> 00:11:06,500 and write it to the data register. 134 00:11:10,570 --> 00:11:15,370 All the IRQs in the slave are not used in this course, we set all the bits 135 00:11:18,030 --> 00:11:19,950 and write it to the data register. 136 00:11:23,950 --> 00:11:27,910 Ok only the IRQ0 of the master will fire interrupts. 137 00:11:30,560 --> 00:11:35,350 The next thing we are going to do is we are going to set the idt entry for the timer. 138 00:11:36,370 --> 00:11:43,030 The vector number of the timer is set to 32 in the PIC, so the address of the entry is 139 00:11:43,030 --> 00:11:46,320 the base of the idt plus 32*16. 140 00:11:46,840 --> 00:11:49,690 Remember each entry takes up 16-byte space. 141 00:11:54,050 --> 00:11:56,750 The handler is called, for example, timer. 142 00:12:00,000 --> 00:12:01,570 Then we can set the offset. 143 00:12:02,370 --> 00:12:04,190 We copy the code here. 144 00:12:06,820 --> 00:12:13,120 You can wrap the code as a function and call the function to set the idt entry. We simply copy and paste here 145 00:12:13,120 --> 00:12:13,480 . 146 00:12:15,610 --> 00:12:21,220 The register rdi holds the address of the idt and we need to add rdi 147 00:12:22,340 --> 00:12:26,880 32*16 to make it point to the timer entry. 148 00:12:27,770 --> 00:12:29,810 Ok. With the idt entry is set, 149 00:12:30,040 --> 00:12:30,510 let’s write the timer handler 150 00:12:30,530 --> 00:12:31,460 . 151 00:12:41,530 --> 00:12:44,530 The process of interrupt handling is pretty much the same. 152 00:12:44,770 --> 00:12:50,890 First off, we save the state of the CPU by pushing the registers on the stack and pop them back 153 00:12:50,890 --> 00:12:52,330 before the handler returns. 154 00:13:03,990 --> 00:13:06,410 The return instruction is interrupt return. 155 00:13:08,180 --> 00:13:12,590 Since this is the first time we test the timer handler, we just print the character 156 00:13:14,900 --> 00:13:19,310 T to indicate that we enter the timer interrupt. 157 00:13:20,930 --> 00:13:23,230 We print the character in different location 158 00:13:29,290 --> 00:13:33,340 and in different color, for example, we choose the color yellow. 159 00:13:35,220 --> 00:13:36,330 And jump to end. 160 00:13:39,430 --> 00:13:45,990 Alright, the last thing we need to do is enable interrupt by using the instruction set interrupt flag 161 00:13:45,990 --> 00:13:46,300 . 162 00:13:47,910 --> 00:13:53,670 Remember when we switch from real mode to protected mode in the loader file, we disabled the interrupt. 163 00:13:54,220 --> 00:13:56,920 The processor will not respond the interrupt request after that. 164 00:13:56,940 --> 00:14:01,080 We have set up the timer and the interrupt controller at this point, 165 00:14:01,080 --> 00:14:07,050 in order to handle the interrupt, we need to enable the interrupt and we are good to go 166 00:14:07,050 --> 00:14:07,350 . 167 00:14:11,950 --> 00:14:15,450 Ok we save the file and build the project. 168 00:14:19,560 --> 00:14:23,070 We opened the boot folder and run the bochs. 169 00:14:25,070 --> 00:14:27,530 OK, as you can see, we have two characters. 170 00:14:28,630 --> 00:14:31,070 The character K and the new character T 171 00:14:32,100 --> 00:14:34,260 So the timer interrupt is actually fired.