1 00:00:00,210 --> 00:00:07,800 Ok, so we are now in protected mode. We will see how to prepare and switch to long mode. 2 00:00:07,800 --> 00:00:15,330 As we have talked about in previous lectures, long mode consists of two sub modes 64-bit mode and compatibility mode 3 00:00:15,330 --> 00:00:17,790 which is not used in our system. 4 00:00:18,750 --> 00:00:20,380 When we switch to long mode, 5 00:00:20,400 --> 00:00:22,540 we are actually running in 64-bit mode. 6 00:00:22,740 --> 00:00:28,500 So in this course, when we talk about long mode, we are in fact referring to 64-bit mode. 7 00:00:29,500 --> 00:00:36,190 Our kernel runs in this mode as well as the application programs. When we jump to the kernel entry, we will stay there 8 00:00:36,190 --> 00:00:38,220 and never leave this mode until we shut down the system. 9 00:00:39,700 --> 00:00:43,960 So all the projects in the following lectures are supposed to be running in long mode. 10 00:00:45,000 --> 00:00:51,630 As we have discussed in the last lecture, protected mode is only used to prepare for long mode. 11 00:00:51,630 --> 00:00:53,500 Immediately after jumping to protected mode, 12 00:00:53,520 --> 00:00:58,980 we prepare and switch to long mode which is a little bit complex than preparing for protected mode 13 00:00:58,980 --> 00:00:59,290 . 14 00:01:00,060 --> 00:01:00,900 Let's take a look. 15 00:01:01,810 --> 00:01:04,959 Preparing for long mode requires several steps 16 00:01:06,470 --> 00:01:10,410 including load gdt and idt, enable paging. 17 00:01:10,880 --> 00:01:18,240 Note that we have to enable paging before entering long mode. Since segmentation is disabled in 64-bit mode, 18 00:01:18,830 --> 00:01:21,380 we use paging to perform memory protection. 19 00:01:22,540 --> 00:01:28,630 Our memory manager relies on the paging mechanism which is discussed in the section memory management. 20 00:01:29,840 --> 00:01:36,950 For now, you can think of it as a technique of memory management, then we can enable Long mode by setting 21 00:01:36,950 --> 00:01:43,880 the specific registers and load the new code segment descriptor in cs register using jump instruction. 22 00:01:44,270 --> 00:01:45,410 And we are good to go. 23 00:01:47,810 --> 00:01:54,680 Gdt is still used in 64-bit mode and the fields of gdt entries are slightly different. 24 00:01:54,680 --> 00:01:57,900 Regarding to segment registers, cs register is used, 25 00:01:58,670 --> 00:02:03,440 however, the contents of ds es and ss registers are all ignored, 26 00:02:03,950 --> 00:02:07,850 the contents in hidden parts of those registers are also ignored. 27 00:02:08,479 --> 00:02:15,550 The memory segment referenced by those segment registers are treated as if the base address is 0 28 00:02:15,560 --> 00:02:22,910 and the size of the segment is the maximum size the processor can address. Actually cpu doesn’t perform segment limit checks 29 00:02:22,910 --> 00:02:24,290 as we saw in protected mode. 30 00:02:24,740 --> 00:02:31,250 So generally we don’t need data segment descriptors. But there is one special case 31 00:02:31,260 --> 00:02:32,830 where we need the data segment descriptors, 32 00:02:33,320 --> 00:02:40,580 that is, when we try to get from ring0 to ring3 to run user applications. 33 00:02:40,730 --> 00:02:48,140 Because getting from ring0 to ring3 requires us to add a valid data segment descriptor to load into ss register. 34 00:02:48,140 --> 00:02:51,200 So code and data segments are used in our operating system. 35 00:02:52,870 --> 00:02:59,530 Another thing I need to mention is that although the contents of ds es and ss registers are ignored, 36 00:02:59,980 --> 00:03:01,940 When loading descriptors into these registers, 37 00:03:01,960 --> 00:03:08,680 the processor still performs checks to see if the descriptor is valid. If it is an invalid descriptor 38 00:03:08,680 --> 00:03:09,430 , 39 00:03:09,760 --> 00:03:16,090 the exception is generated. So we don’t mess with segment registers. 40 00:03:18,900 --> 00:03:22,530 As you can see, the content in ds register is ignored 41 00:03:23,470 --> 00:03:27,630 and the base address is always 0. And then add it to the offset. 42 00:03:29,730 --> 00:03:31,590 The address we get is 1000. 43 00:03:32,790 --> 00:03:36,920 So the process is simplified compared to that in protected mode. 44 00:03:38,260 --> 00:03:40,090 As for the interrupt processing, 45 00:03:41,670 --> 00:03:48,540 the interrupt descriptor table is still used in 64-bit mode. Each entry takes up 16 bytes, whereas in protected mode 46 00:03:48,540 --> 00:03:49,550 , 47 00:03:49,590 --> 00:03:51,680 the idt entry is 8 bytes long. 48 00:03:52,590 --> 00:03:56,280 The process of interrupting handling is the same as in protected mode. 49 00:03:57,090 --> 00:04:04,800 In this example, the interrupt request number 3 is fired, cpu gets the corresponding item in the IDT. 50 00:04:05,670 --> 00:04:09,450 The idt entry also references the code segment the interrupt handler is at. 51 00:04:09,450 --> 00:04:12,260 The base address of the code segment is always 0 52 00:04:12,270 --> 00:04:20,269 and add it to the offset which is specified in idt entry, 5000 in this example. 53 00:04:21,060 --> 00:04:23,280 Then we get the address of interrupt handler. 54 00:04:27,460 --> 00:04:34,000 The privilege levels are the same as what we have discussed in the previous lecture. 55 00:04:34,000 --> 00:04:40,090 Our kernel and user programs run in ring0 and ring3 respectively and leaves ring1 and ring2 unused 56 00:04:40,090 --> 00:04:41,150 . 57 00:04:41,800 --> 00:04:44,860 The structure of the segment selectors is not changed. 58 00:04:46,000 --> 00:04:51,710 If we want to load a descriptor into segment register, the index should point to a valid descriptor 59 00:04:53,520 --> 00:05:00,810 and cpl rpl will compare against dpl in the descriptor. In 64-bit mode, 60 00:05:00,860 --> 00:05:07,110 it’s really rare to load segment descriptors by ourselves. In our system, we load code segment descriptors when we jump to 64-bit mode 61 00:05:07,110 --> 00:05:08,070 . 62 00:05:08,520 --> 00:05:11,790 And load data segment descriptor in ss register 63 00:05:12,060 --> 00:05:18,590 when we get to ring3 for the first time, which are the only two scenarios 64 00:05:18,600 --> 00:05:20,100 where we load the segment descriptors by ourselves. 65 00:05:21,600 --> 00:05:24,440 Anyway, let’s see the descriptor formats in detail. 66 00:05:27,600 --> 00:05:33,810 As you see, the fields in dark grey are ignored which leaves us only few bits in attributes field to set 67 00:05:33,810 --> 00:05:34,440 . 68 00:05:35,690 --> 00:05:42,920 The fields which are set 1 indicate that the descriptor is code segment descriptor. 69 00:05:42,920 --> 00:05:48,740 C is conforming bit, as we have talked about in the last lecture, we only use non-conforming code segment in the system 70 00:05:48,740 --> 00:05:49,270 . 71 00:05:50,780 --> 00:05:58,460 The DPL indicates privilege level of the segment. We will set it to 0. After we load the code segment 72 00:05:58,460 --> 00:05:59,030 into cs register 73 00:05:59,030 --> 00:06:01,820 and jump to 64-bit mode, 74 00:06:02,060 --> 00:06:05,750 the cpl will be 0 meaning that we are at ring0. 75 00:06:06,810 --> 00:06:13,650 Move to the next bit which is P present bit which should be 1. Otherwise the cpu exception 76 00:06:13,650 --> 00:06:16,320 is generated when we load the descriptor. 77 00:06:17,980 --> 00:06:23,740 L stands for long which is new attribute bit. If the bit is set to 1, 78 00:06:23,740 --> 00:06:27,030 it means that we are running in 64-bit mode. 79 00:06:27,070 --> 00:06:31,140 If it is 0, we are running in compatibility mode. As for the D bit, 80 00:06:31,710 --> 00:06:35,680 the only valid value is 0 when we set the long bit to 1. 81 00:06:38,630 --> 00:06:45,590 The data segment descriptor also has very few bits we can set. The field 10 means that 82 00:06:45,590 --> 00:06:49,860 the descriptor is data segment descriptor. W stands for writeable. 83 00:06:50,840 --> 00:06:52,190 We should set it 1. 84 00:06:52,970 --> 00:06:58,320 If we load the descriptor in ss register with w bit set to 0, 85 00:06:59,090 --> 00:07:00,530 then we will get cpu exception. 86 00:07:02,040 --> 00:07:06,420 P and dpl has the same meaning as those in code segment descriptor. 87 00:07:07,680 --> 00:07:10,770 What we are going to talk about next is memory address. 88 00:07:14,200 --> 00:07:20,410 There are two terms virtual address and physical address. The virtual address is basically we see in our code 89 00:07:20,410 --> 00:07:24,590 and these addresses do not directly go to the memory bus. 90 00:07:25,180 --> 00:07:31,720 The virtual addresses need to be mapped to physical addresses by using memory management unit or MMU. 91 00:07:32,830 --> 00:07:38,410 The physical address is the address put on the memory bus and then the data in that memory location is accessed 92 00:07:38,410 --> 00:07:39,040 . 93 00:07:39,790 --> 00:07:44,070 There are only few cases where we use physical address in our code directly. 94 00:07:44,270 --> 00:07:46,960 We will see one of these cases in this lecture. 95 00:07:47,980 --> 00:07:48,710 In 64-bit mode, 96 00:07:49,120 --> 00:07:55,600 we have 64 bits of virtual address space. But not all the virtual addresses are available to us. 97 00:07:56,730 --> 00:08:02,590 The canonical address is the address with the bits 63 through to the most-significant implemented bit 98 00:08:02,650 --> 00:08:10,200 set to either all ones or all zeros. For example, suppose we have a 48-bit address here, 99 00:08:10,200 --> 00:08:13,620 from bit 0 to bit 47. 100 00:08:13,650 --> 00:08:16,680 Bit 47 here is the most-significant implemented bit, 101 00:08:17,220 --> 00:08:24,870 so we can only set the upper 16 bits which is from bit 48 to bit 63 to all 1s. 102 00:08:26,750 --> 00:08:32,840 Other value in the upper 16 bits in this case will be considered as an invalid address or non-canonical address. 103 00:08:32,840 --> 00:08:36,710 If we, for example, have 0 in bit 47 104 00:08:38,190 --> 00:08:41,370 then we can only set the upper 16bits to all zeros. 105 00:08:43,190 --> 00:08:48,020 Generally, when we use non-canonical address, the cpu exception is generated. 106 00:08:49,070 --> 00:08:56,160 Also, note that intel has added support for 57-bit virtual address in recent processors 107 00:08:56,160 --> 00:08:59,850 which are able to address 128 petabyte memory space. 108 00:09:00,750 --> 00:09:06,300 But in this course, our code is based upon the assumption that 109 00:09:06,300 --> 00:09:10,420 the processors only support 48 bit virtual address so that our system can run on older machines. 110 00:09:11,130 --> 00:09:15,180 Now let’s see the memory map setup for our system in 64-bit mode. 111 00:09:18,540 --> 00:09:26,360 The user space and kernel space are the address space available to us. As you see, the addresses in red 112 00:09:26,370 --> 00:09:31,510 are the address range where all the addresses are non-canonical address which cannot be used. 113 00:09:32,070 --> 00:09:35,340 You can check out the bit 47 and the upper 16 bits of these address yourself 114 00:09:35,340 --> 00:09:41,100 and you will find out they doesn’t conform to the canonical address. 115 00:09:42,550 --> 00:09:46,540 and our kernel will be placed at the higher part of the memory space. 116 00:09:47,910 --> 00:09:53,130 Ok we start from protected mode and remove the code of printing characters. 117 00:09:56,090 --> 00:10:01,970 The first thing we are going to do is we are going to set up paging. Enable long mode requires us 118 00:10:01,970 --> 00:10:03,720 to setup and enable paging. 119 00:10:04,160 --> 00:10:10,600 Paging is another major subject we will discuss later in this course. For the moment, we just write the code as is here 120 00:10:10,610 --> 00:10:11,840 . 121 00:10:12,560 --> 00:10:14,840 So we just copy and paste the code here. 122 00:10:21,550 --> 00:10:27,970 This block of code basically finds a free memory area and initialize the paging structure 123 00:10:27,970 --> 00:10:30,760 which is used to translate the virtual address to physical address. 124 00:10:32,170 --> 00:10:39,250 Then we load the gdt that we will use in 64-bit mode. Just like we did in protected mode, 125 00:10:39,250 --> 00:10:40,540 we define a gdt pointer. 126 00:10:46,410 --> 00:10:47,370 So we call it 127 00:10:49,380 --> 00:10:50,600 gdt 64 pointer 128 00:10:54,230 --> 00:10:56,150 The first two bytes are the length of gdt 129 00:11:02,500 --> 00:11:05,190 and the next four bytes are the address of gdt. 130 00:11:09,980 --> 00:11:12,290 Let’s define the global descriptor table. 131 00:11:16,020 --> 00:11:18,300 We call it gdt64 132 00:11:22,190 --> 00:11:26,500 The first entry of it is empty so we simply assign 0 to it. 133 00:11:29,100 --> 00:11:35,820 The second entry is used for code segment. Setting it up is relatively simple because most of the fields are ignored 134 00:11:36,000 --> 00:11:36,710 . 135 00:11:37,350 --> 00:11:39,640 There are only some of attributes left to us. 136 00:11:40,270 --> 00:11:46,850 As you can see, the conforming bit is set to 0 since we only use non-conforming code segment in our system. 137 00:11:48,010 --> 00:11:55,570 The next 1s means that the descriptor is code segment descriptor. The DPL indicates privilege level of the segment. 138 00:11:55,570 --> 00:12:03,160 We set dpl to 0, when we load the descriptor to cs register and jump to 64-bit mode. 139 00:12:03,760 --> 00:12:07,630 the cpl will be 0 indicating that we are at ring0. 140 00:12:09,330 --> 00:12:15,780 Present bit is set to 1, otherwise the cpu exception is generated if we try to load the descriptor. 141 00:12:17,340 --> 00:12:23,970 The Long bit should be 1 indicating that the code segment runs in 64-bit mode. 142 00:12:23,970 --> 00:12:25,700 If it is 0, then it runs in compatibility mode. 143 00:12:26,790 --> 00:12:30,620 The D bit can only be set to 0 if the long bit is set. 144 00:12:31,650 --> 00:12:35,310 With all the fields set, we assign the value to the second entry. 145 00:12:39,680 --> 00:12:46,300 That’s all we need for running in 64-bit mode for the moment. If we have no plan to jump to ring3, 146 00:12:46,340 --> 00:12:49,670 we don’t need data segment for accessing data in memory. 147 00:12:50,750 --> 00:12:55,790 In the kernel file, we will sort out the data we collect here and reload the gdt and idt. 148 00:12:56,810 --> 00:12:59,940 So here in the loader file, there is no need to have data segment in gdt, 149 00:12:59,940 --> 00:13:03,560 since we are running in ring0. 150 00:13:05,870 --> 00:13:09,440 Alright, let’s continue. We define a constant 151 00:13:13,440 --> 00:13:14,530 gdt64length 152 00:13:20,630 --> 00:13:23,610 and assign to gdt64 pointer. 153 00:13:23,940 --> 00:13:28,740 OK, now we can use the instruction lgdt to load gdt pointer. 154 00:13:31,790 --> 00:13:37,010 The next thing we are going to do is we are going to enable 64-bit mode by setting the necessary bits 155 00:13:37,010 --> 00:13:38,540 in some registers. 156 00:13:39,170 --> 00:13:46,370 The first register is register cr4. The bit 5 in cr4 is called physical address extension or PAE bit. 157 00:13:46,570 --> 00:13:51,890 We have to set it to 1 before activating 64-bit mode. 158 00:13:52,370 --> 00:13:55,870 So we copy the content of cr4 to eax 159 00:13:59,930 --> 00:14:02,300 and set bit 5 using or instruction. 160 00:14:07,180 --> 00:14:09,610 then move it back to cr4 register. 161 00:14:12,520 --> 00:14:19,300 The next register is related to cr3 register, we copy the address of the page structure we just set up, 162 00:14:19,300 --> 00:14:23,740 80000 in this case to cr3 register. 163 00:14:30,700 --> 00:14:33,670 The address loaded to cr3 are physical address. 164 00:14:34,000 --> 00:14:38,380 So this is one of few cases we need to use physical address in our code. 165 00:14:39,750 --> 00:14:46,170 By the way, all the addresses we have seen so far are all physical addresses because we don’t enable paging. 166 00:14:46,560 --> 00:14:52,560 After we enable paging and enter long mode, the addresses in our code need to be mapped to physical addresses first 167 00:14:52,560 --> 00:14:54,740 and then sent to the bus. 168 00:14:54,960 --> 00:15:01,260 But if we load new address in cr3 register, we still need physical address instead of virtual address 169 00:15:01,260 --> 00:15:01,710 . 170 00:15:03,310 --> 00:15:04,500 OK, let's move on. 171 00:15:07,320 --> 00:15:13,800 Then we can enable long mode. There is a model specific register called extended feature enable register 172 00:15:14,070 --> 00:15:14,730 , 173 00:15:14,730 --> 00:15:20,780 and the bit 8 is long mode enable bit which should be set. To read and write a model specific register, 174 00:15:21,030 --> 00:15:23,760 we move the index of the register to ecx, 175 00:15:27,800 --> 00:15:31,520 c0000080 in this case 176 00:15:33,070 --> 00:15:34,900 and execute read msr. 177 00:15:37,040 --> 00:15:42,890 The return value is in eax register. So we set the bit 8 in eax using or instruction. 178 00:15:47,690 --> 00:15:51,530 Then write it back to the same register by using writer msr. 179 00:15:54,660 --> 00:16:02,400 Ok the last thing we will do is enable paging by setting the bit 31 in register cr0. 180 00:16:02,400 --> 00:16:04,920 So we copy the content of cr0 to eax 181 00:16:08,660 --> 00:16:10,340 and set the bit 31 182 00:16:15,990 --> 00:16:17,600 and write it back to cr0. 183 00:16:22,270 --> 00:16:23,670 The long mode is activated now. 184 00:16:23,770 --> 00:16:28,630 Let’s load the new code segment descriptor by jumping to the long mode entry. 185 00:16:30,450 --> 00:16:37,320 Here we specify the segment selector 8, since each entry is 8 bytes and the code segment selector is 186 00:16:37,350 --> 00:16:42,330 the second entry and then the offset long mode entry 187 00:16:44,710 --> 00:16:48,300 We haven’t defined the label long mode entry. Let’s do it now. 188 00:16:52,790 --> 00:16:54,530 don’t forget the directive bits 189 00:16:57,280 --> 00:17:00,370 to indicate that we are now running at 64-bit mode. 190 00:17:02,220 --> 00:17:03,210 So let's define 191 00:17:04,319 --> 00:17:06,160 the label long mode entry 192 00:17:09,440 --> 00:17:13,550 We don’t need to initialize ds es and ss registers. 193 00:17:14,690 --> 00:17:22,130 So here we only initialize the stack pointer. We still set it to 7c00. We move rsp 194 00:17:25,000 --> 00:17:32,620 7c00. Just like we did when we enter protected mode, we print a character L on the screen 195 00:17:32,620 --> 00:17:34,870 to let us know we jump to long mode successfully. 196 00:17:43,760 --> 00:17:48,050 After printing the character, we end our system by jumping to the infinite loop. 197 00:17:50,280 --> 00:17:52,500 We defined the label Lend. 198 00:17:58,440 --> 00:18:03,660 Ok let’s build the project and test it in the virtual machine. We save the file. 199 00:18:06,200 --> 00:18:09,160 And in the terminal we run the script. 200 00:18:12,230 --> 00:18:15,410 Let's open the boot folder and run bochs. 201 00:18:18,940 --> 00:18:22,210 OK, as you can see, L is printed on screen. 202 00:18:24,330 --> 00:18:29,610 This is the message printed in the real machine, so we successfully enter the long mode. 203 00:18:30,870 --> 00:18:32,310 That's it for this lecture. 204 00:18:32,340 --> 00:18:33,690 See you in the next video.