fix issue 31 (#32)
[lunaix-os.git] / docs / tutorial / 6-acpi_and_apic.md
1 ## 准备工作
2
3 ```sh
4 git checkout 287a5f7ae6a3bec3d679a5de394e915b56c7367d
5 ```
6
7 观看对应视频。本次涉及到四个视频,内容很多。建议反复观看加深印象。
8
9 ## 源码分析
10
11 ### 新增代码
12
13 我们可以看到kernel/k_init.c的`_kernel_pre_init`添加了`intr_routine_init();`、`rtc_init();`,`_kernel_post_init`添加了`acpi_init`、`apic_init();`、`ioapic_init();`、`timer_init(SYS_TIMER_FREQUENCY_HZ);`。本次就主要分析这些代码。
14
15 ### kernel/asm/x86/interrupt.S
16
17 lunaix-os的中断处理有了一些改进。来看看kernel/asm/x86/interrupt.S里面的`interrupt_wrapper`。
18
19 ```assembly
20     interrupt_wrapper:
21         pushl %esp
22         pushl %esi
23         pushl %ebp
24         pushl %edi
25         pushl %edx
26         pushl %ecx
27         pushl %ebx
28         pushl %eax
29
30         movl %esp, %eax
31         andl $0xfffffff0, %esp
32         subl $16, %esp
33         movl %eax, (%esp)
34
35         call intr_handler
36         popl %esp
37
38         popl %eax
39         popl %ebx
40         popl %ecx
41         popl %edx
42         popl %edi
43         popl %ebp
44         popl %esi
45         popl %esp
46
47         addl $8, %esp
48
49         iret
50 ```
51
52 首先保存寄存器,然后继续栈对齐,接着调用`intr_handler`,最后恢复栈从而`iret`。注意interrupt_wrapper第一条指令前的栈是push了error code的中断栈。上面代码是在中断栈的顶上来保持寄存器的。所以pop出保存的寄存器后就可以iret。
53
54 `intr_handler`参数是`isr_param`类型,根据默认`cdecl`调用约定,参数由调用者布置到栈上。所以`param`指向的是之前push的寄存器值和中断栈内容。
55
56 ```c
57 typedef struct {
58     gp_regs registers;
59     unsigned int vector;
60     unsigned int err_code;
61     unsigned int eip;
62     unsigned int cs;
63     unsigned int eflags;
64     unsigned int esp;
65     unsigned int ss;
66 } __attribute__((packed)) isr_param;
67
68 typedef struct
69 {
70     reg32 eax;
71     reg32 ebx;
72     reg32 ecx;
73     reg32 edx;
74     reg32 edi;
75     reg32 ebp;
76     reg32 esi;
77     reg32 esp;
78 } __attribute__((packed)) gp_regs;
79 ```
80
81 ### kernel/asm/x86/interrupts.c
82
83 这个函数会根据vector来调用不同中断处理函数。这样改动的好处是可以切换同一个中断号的中断处理函数。最后是`apic`的特殊处理,见注释。
84
85 ```c
86 void
87 intr_handler(isr_param* param)
88 {
89     if (param->vector <= 255) {
90         int_subscriber subscriber = subscribers[param->vector];
91         if (subscriber) {
92             subscriber(param);
93             goto done;
94         }
95     }
96
97     if (fallback) {
98         fallback(param);
99         goto done;
100     }
101     
102     kprint_panic("INT %u: (%x) [%p: %p] Unknown",
103             param->vector,
104             param->err_code,
105             param->cs,
106             param->eip);
107
108 done:
109     // for all external interrupts except the spurious interrupt
110     //  this is required by Intel Manual Vol.3A, section 10.8.1 & 10.8.5
111     if (param->vector >= EX_INTERRUPT_BEGIN && param->vector != APIC_SPIV_IV) {
112         apic_done_servicing();
113     }
114     return;
115 }
116 ```
117
118 `intr_routine_init`就是用来注册不同vector对应函数的。
119
120 ```c
121 void
122 intr_routine_init() 
123 {
124     intr_subscribe(FAULT_DIVISION_ERROR,     intr_routine_divide_zero);
125     intr_subscribe(FAULT_GENERAL_PROTECTION, intr_routine_general_protection);
126     intr_subscribe(FAULT_PAGE_FAULT,         intr_routine_page_fault);
127     intr_subscribe(LUNAIX_SYS_PANIC,         intr_routine_sys_panic);
128     intr_subscribe(APIC_SPIV_IV,             intr_routine_apic_spi);
129     intr_subscribe(APIC_ERROR_IV,            intr_routine_apic_error);
130
131     intr_set_fallback_handler(intr_set_fallback_handler);
132 }
133
134 void
135 intr_subscribe(const uint8_t vector, int_subscriber subscriber) {
136     subscribers[vector] = subscriber;
137 }
138 ```
139
140 现在可以写一个中断的小结。如果我们要添加新的中断处理,需要
141
142 1、写好中断处理函数,参数为`const isr_param* param`。因为subscribers是类型`typedef void (*int_subscriber)(isr_param*);`的数组
143
144 2、在`intr_routine_init`注册
145
146 3、`isr_template xxx_xxx`利用模板来push error code
147
148 ### rtc_init
149
150 下面实现了RTC寄存器的读写操作,本质是对端口的读写操作。
151
152 ```c
153 uint8_t
154 rtc_read_reg(uint8_t reg_selector)
155 {
156     io_outb(RTC_INDEX_PORT, reg_selector);
157     return io_inb(RTC_TARGET_PORT);
158 }
159
160 void
161 rtc_write_reg(uint8_t reg_selector, uint8_t val)
162 {
163     io_outb(RTC_INDEX_PORT, reg_selector);
164     io_outb(RTC_TARGET_PORT, val);
165 }
166
167 static inline void
168 io_outb(int port, uint8_t data)
169 {
170     asm volatile("outb %0, %w1" : : "a"(data), "d"(port));
171 }
172
173 static inline uint8_t
174 io_inb(int port)
175 {
176     uint8_t data;
177     asm volatile("inb %w1,%0" : "=a"(data) : "d"(port));
178     return data;
179 }
180 ```
181
182 根据资料
183
184 > The RTC contains two sets of indexed registers that are accessed using the two
185 > separate Index and Target registers (70h/71h or 72h/73h)[1]
186
187 寄存器的端口要通过基址和偏移获得。rtc_read_reg会先往端口`RTC_INDEX_PORT`(0x70)写入想操作的寄存器偏移(reg_selector),再读RTC_TARGET_PORT(0x71)。这样才能读到寄存器的值。这样设计可能是为了节省端口数量。如果一个寄存器占用一个端口就太浪费了。Register A实际上可分为8个bits。其中低四位bits可控制频率[2]。WITH_NMI_DISABLED视频也说了,是因为某些遗留问题。RTC_FREQUENCY_1024HZ用于控制频率。RTC_DIVIDER_33KHZ用于频率计算。具体可查看mc146818a(cmos-rtc)。
188
189 ```c
190 void
191 rtc_init() {
192     uint8_t regA = rtc_read_reg(RTC_REG_A | WITH_NMI_DISABLED);
193     regA = (regA & ~0x7f) | RTC_FREQUENCY_1024HZ | RTC_DIVIDER_33KHZ;
194     rtc_write_reg(RTC_REG_A | WITH_NMI_DISABLED, regA);
195
196     // Make sure the rtc timer is disabled by default
197     rtc_disable_timer();
198 }
199 ```
200
201 关闭timer也是一个位操作。可以看资料里面寄存器B的结构。
202
203 ```c
204 void
205 rtc_disable_timer() {
206     uint8_t regB = rtc_read_reg(RTC_REG_B | WITH_NMI_DISABLED);
207     rtc_write_reg(RTC_REG_B | WITH_NMI_DISABLED, regB & ~RTC_TIMER_ON);
208 }
209 ```
210
211 ### acpi_init
212
213 这个函数用于找到`ACPI`信息,保存信息到结构体`acpi_context`。
214
215 ```c
216 int
217 acpi_init(multiboot_info_t* mb_info)
218 {
219     acpi_rsdp_t* rsdp = acpi_locate_rsdp(mb_info);
220
221     assert_msg(rsdp, "Fail to locate ACPI_RSDP");
222     assert_msg(acpi_rsdp_validate(rsdp), "Invalid ACPI_RSDP (checksum failed)");
223
224     kprintf(KINFO "RSDP found at %p, RSDT: %p\n", rsdp, rsdp->rsdt);
225
226     acpi_rsdt_t* rsdt = rsdp->rsdt;
227 ```
228
229 对于`acpi_rsdp_t`和`acpi_rsdt_t`可以在一些网站上找到它们的结构[3]。
230
231 **RSDP Structure**第一个字段是`Signature`,即字符串`"RSD PTR"`。因为之前低地址对等映射了,我们可以在低1MiB的空间暴力搜索这个字符串。如果找到了,那么大概了说明找到了RSDP。
232
233 还要通过acpi_rsdp_validate进行验证是否有效。这里无效的话可以再往后搜索,不过这种情况概率很小。
234
235 视频中提到virtual box启动内核时,RSDP会存储在0xe0000,上面网站有提到。
236
237 > #### 5.2.5.1. Finding the **RSDP** on IA-PC Systems
238 >
239 > ...
240 >
241 > - The BIOS read-only memory space between 0E0000h and 0FFFFFh.
242
243 复制信息到`acpi_context`
244
245 ```c
246         toc = lxcalloc(1, sizeof(acpi_context));
247     assert_msg(toc, "Fail to create ACPI context");
248
249     strncpy(toc->oem_id, rsdt->header.oem_id, 6);
250     toc->oem_id[6] = '\0';
251 ```
252
253 `acpi_sdthdr_t`、`acpi_madt_t`的结构也能从上面的网站找到。
254
255 ```c
256 size_t entry_n = (rsdt->header.length - sizeof(acpi_sdthdr_t)) >> 2;
257     for (size_t i = 0; i < entry_n; i++) {
258         acpi_sdthdr_t* sdthdr = ((acpi_apic_t**)&(rsdt->entry))[i];
259         switch (sdthdr->signature) {
260             case ACPI_MADT_SIG:
261                 madt_parse((acpi_madt_t*)sdthdr, toc);
262                 break;
263             default:
264                 break;
265         }
266     }
267 ```
268
269 保存MADT中的APIC信息到context。简单来说就是保存acpi_apic_t、acpi_ioapic_t、acpi_intso_t这三个结构体信息。
270
271 ```c
272 madt_parse((acpi_madt_t*)sdthdr, toc);
273 ```
274
275 打印保存的信息
276
277 ```c
278     kprintf(KINFO "OEM: %s\n", toc->oem_id);
279     kprintf(KINFO "IOAPIC address: %p\n", toc->madt.ioapic->ioapic_addr);
280     kprintf(KINFO "APIC address: %p\n", toc->madt.apic_addr);
281
282     for (size_t i = 0; i < 24; i++) {
283         acpi_intso_t* intso = toc->madt.irq_exception[i];
284         if (!intso)
285             continue;
286
287         kprintf(KINFO "IRQ #%u -> GSI #%u\n", intso->source, intso->gsi);
288     }
289 ```
290
291 标记为占用,再映射一下地址。
292
293 ```c
294 acpi_init(_k_init_mb_info);
295     uintptr_t ioapic_addr = acpi_get_context()->madt.ioapic->ioapic_addr;
296
297     pmm_mark_page_occupied(FLOOR(__APIC_BASE_PADDR, PG_SIZE_BITS));
298     pmm_mark_page_occupied(FLOOR(ioapic_addr, PG_SIZE_BITS));
299
300     vmm_set_mapping(APIC_BASE_VADDR, __APIC_BASE_PADDR, PG_PREM_RW);
301     vmm_set_mapping(IOAPIC_BASE_VADDR, ioapic_addr, PG_PREM_RW);
302 ```
303
304 ### apic_init
305
306 寄存器操作需要基址加偏移
307
308 ```c
309 #define apic_read_reg(reg)           (*(uint32_t*)(APIC_BASE_VADDR + (reg)))
310 #define apic_write_reg(reg, val)     (*(uint32_t*)(APIC_BASE_VADDR + (reg)) = (val))
311 ```
312
313 基址的物理地址是0xFEE00000,见Intel手册Figure 10-8. Local Vector Table (LVT)中右下角[4]。
314
315 ```C
316 #define APIC_BASE_VADDR 0x1000
317 #define __APIC_BASE_PADDR 0xFEE00000
318 ```
319
320 该函数的具体操作见视频**7.1 外中断与APIC(P1)**讲解。
321
322 ### ioapic_init
323
324 初始化后RTC_TIMER_IV这个vector属于timer了,这里要用到之前保存的acpi_ctx。
325
326 ```c
327 void
328 ioapic_init() {
329     // Remapping the IRQs
330     
331     acpi_context* acpi_ctx = acpi_get_context();
332
333     // Remap the IRQ 8 (rtc timer's vector) to RTC_TIMER_IV in ioapic
334     //       (Remarks IRQ 8 is pin INTIN8)
335     //       See IBM PC/AT Technical Reference 1-10 for old RTC IRQ
336     //       See Intel's Multiprocessor Specification for IRQ - IOAPIC INTIN mapping config.
337     
338     // The ioapic_get_irq is to make sure we capture those overriden IRQs
339
340     // PC_AT_IRQ_RTC -> RTC_TIMER_IV, fixed, edge trigged, polarity=high, physical, APIC ID 0
341     ioapic_redirect(ioapic_get_irq(acpi_ctx, PC_AT_IRQ_RTC), RTC_TIMER_IV, 0, IOAPIC_DELMOD_FIXED);
342 }
343 ```
344
345 ### timer_init
346
347 接下来大概看看timer相关结构体。
348
349 ```c
350 struct lx_timer_context {
351     struct lx_timer *active_timers;
352     uint32_t base_frequency;
353     uint32_t running_frequency;
354     uint32_t tick_interval;
355 };
356
357 struct lx_timer {
358     struct llist_header link;
359     uint32_t deadline;
360     uint32_t counter;
361     void* payload;
362     void (*callback)(void*);
363     uint8_t flags;
364 };
365 ```
366
367 `lx_timer_context`可以链接`lx_timer`,`lx_timer`可以通过`llist_header`链接其他`lx_timer`。每个`lx_timer`有一个回调函数。
368
369 `timer_init_context`会动态分配`lx_timer_context`,然后挂一个`lx_timer`
370
371 ```c
372 void
373 timer_init(uint32_t frequency)
374 {
375     timer_init_context();
376     //...
377     
378 void
379 timer_init_context()
380 {
381     timer_ctx =
382       (struct lx_timer_context*)lxmalloc(sizeof(struct lx_timer_context));
383
384     assert_msg(timer_ctx, "Fail to initialize timer contex");
385
386     timer_ctx->active_timers =
387       (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
388     llist_init_head(timer_ctx->active_timers);
389 }
390 ```
391
392 接下来配置timer。LVT的Timer结构见Figure 10-8. Local Vector Table (LVT)[4]上面部分。APIC_TIMER_DIV64见Figure 10-10. Divide Configuration Register[5],作用在视频中讲过。
393
394 > In one-shot mode, the
395 > timer is started by programming its initial-count register. The initial count value is then copied into the current-
396 > count register and count-down begins. After the timer reaches zero, a timer interrupt is generated and the timer
397 > remains at its 0 value until reprogrammed.[6]
398
399 ```c
400         cpu_disable_interrupt();
401
402     // Setup APIC timer
403
404     // Setup a one-shot timer, we will use this to measure the bus speed. So we
405     // can
406     //   then calibrate apic timer to work at *nearly* accurate hz
407     apic_write_reg(APIC_TIMER_LVT,
408                    LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_ONESHOT));
409
410     // Set divider to 64
411     apic_write_reg(APIC_TIMER_DCR, APIC_TIMER_DIV64);
412 ```
413
414 接下来是计算和保存timer的频率。`temp_intr_routine_apic_timer`、`temp_intr_routine_rtc_tick`需要单独分析。
415
416 ```c
417     timer_ctx->base_frequency = 0;
418     rtc_counter = 0;
419     apic_timer_done = 0;
420
421     intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
422     intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
423
424     rtc_enable_timer();                                         // start RTC timer
425     apic_write_reg(APIC_TIMER_ICR, APIC_CALIBRATION_CONST);     // start APIC timer
426
427     // enable interrupt, just for our RTC start ticking!
428     cpu_enable_interrupt();
429
430     wait_until(apic_timer_done);
431
432     // cpu_disable_interrupt();
433
434     assert_msg(timer_ctx->base_frequency, "Fail to initialize timer (NOFREQ)");
435
436     kprintf(KINFO "Base frequency: %u Hz\n", timer_ctx->base_frequency);
437
438     timer_ctx->running_frequency = frequency;
439     timer_ctx->tick_interval = timer_ctx->base_frequency / frequency;
440 ```
441
442 取消注册
443
444 ```c
445     // cleanup
446     intr_unsubscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
447     intr_unsubscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
448 ```
449
450 LVT_TIMER_PERIODIC作用是可以周期性地产生中断。LVT_TIMER_ONESHOT是一次性的。
451
452 > In periodic mode, the timer is started by writing to the initial-count register (as in one-shot mode), and the value
453 > written is copied into the current-count register, which counts down. The current-count register is automatically
454 > reloaded from the initial-count register when the count reaches 0 and a timer interrupt is generated, and the count-
455 > down is repeated. If during the count-down process the initial-count register is set, counting will restart, using the
456 > new initial-count value. The initial-count register is a read-write register; the current-count register is read only.[6]
457
458 `APIC_TIMER_IV`的handler换成`timer_update`。
459
460 ```c
461     apic_write_reg(APIC_TIMER_LVT,
462                    LVT_ENTRY_TIMER(APIC_TIMER_IV, LVT_TIMER_PERIODIC));
463     intr_subscribe(APIC_TIMER_IV, timer_update);
464 ```
465
466 设置timer触发中断所需要的tick次数。initial-count register结构见Figure 10-11. Initial Count and Current Count Registers[7]。
467
468 ```c
469 apic_write_reg(APIC_TIMER_ICR, timer_ctx->tick_interval);
470 ```
471
472 ### 测量CPU时钟频率
473
474 `temp_intr_routine_apic_timer`、`temp_intr_routine_rtc_tick`
475
476 ```c
477     intr_subscribe(APIC_TIMER_IV, temp_intr_routine_apic_timer);
478     intr_subscribe(RTC_TIMER_IV, temp_intr_routine_rtc_tick);
479 ```
480
481 根据资料,为了保证timer中断被处理,要读取Register C一次,才能清除里面的值,才能发生下一次中断。发生一次中断`temp_intr_routine_rtc_tick`会计数一次。希望读者遇到疑问后能通过查文档解决它。
482
483 > All bits which are high when read by the program are cleared, and new interrupts (on any bits) are held after the read cycle.[8]
484
485 ```c
486 static void
487 temp_intr_routine_rtc_tick(const isr_param* param)
488 {
489     rtc_counter++;
490
491     // dummy read on register C so RTC can send anther interrupt
492     //  This strange behaviour observed in virtual box & bochs
493     (void)rtc_read_reg(RTC_REG_C);
494 }
495 ```
496
497 手动测量需要一个固定且已知频率时钟(RTC Timer)作为参考。用RTC Timer来推测LAPIC Timer。
498
499 开启测量后,ICR会随CPU Timer tick一次减小一次,`ICR`减到0后,`APIC_TIMER_IV`触发。这时获得RTC Timer中断次数`rtc_counter`。我们知道RTC Timer中断一次所需时间`RTC_TIMER_BASE_FREQUENCY`,那么就能知道CPU Timer tick ICR 次所花时间。
500
501 `temp_intr_routine_apic_timer`用于计算CPU Timer每秒tick次数timer_ctx->base_frequency。
502
503 ```c
504 static void
505 temp_intr_routine_apic_timer(const isr_param* param)
506 {
507     timer_ctx->base_frequency =
508       APIC_CALIBRATION_CONST / rtc_counter * RTC_TIMER_BASE_FREQUENCY;
509     apic_timer_done = 1;
510
511     rtc_disable_timer();
512 }
513 ```
514
515 如果我们把`timer_ctx->base_frequency`写入到`APIC_TIMER_ICR`,就会一秒一次中断。但是我们要设置一秒`SYS_TIMER_FREQUENCY_HZ`次中断。
516
517 ```c
518 timer_init(SYS_TIMER_FREQUENCY_HZ);
519 ```
520
521 所以就要写入`timer_ctx->tick_interval`到`APIC_TIMER_ICR`。
522
523 ```c
524     timer_ctx->running_frequency = frequency;
525     timer_ctx->tick_interval = timer_ctx->base_frequency / frequency;
526 ```
527
528 ### timer
529
530 `timer_run_second`可以每秒运行一次callback。如果我们callback是获取并打印时间,那么就能实现时间显示的功能。相信相关代码读者有能力分析。
531
532 ```c
533 int
534 timer_run_second(uint32_t second, void (*callback)(void*), void* payload, uint8_t flags)
535 {
536     return timer_run(second * timer_ctx->running_frequency, callback, payload, flags);
537 }
538 ```
539
540 `timer_run`把callback链接到timer_ctx->active_timers,ticks用于控制callback执行频率。
541
542 ```c
543 int
544 timer_run(uint32_t ticks, void (*callback)(void*), void* payload, uint8_t flags)
545 {
546     struct lx_timer* timer = (struct lx_timer*)lxmalloc(sizeof(struct lx_timer));
547
548     if (!timer) return 0;
549
550     timer->callback = callback;
551     timer->counter = ticks;
552     timer->deadline = ticks;
553     timer->payload = payload;
554     timer->flags = flags;
555
556     llist_append(timer_ctx->active_timers, &timer->link);
557
558     return 1;
559 }
560 ```
561
562 `llist_for_each`宏读者可以自己看看。`timer_update`是timer中断handler。
563
564 `pos->counter`表示花多少tick执行一次。`pos->flags`设置这个callback是一次性的。
565
566 ```c
567 static void
568 timer_update(const isr_param* param)
569 {
570     struct lx_timer *pos, *n;
571     struct lx_timer* timer_list_head = timer_ctx->active_timers;
572
573     llist_for_each(pos, n, &timer_list_head->link, link)
574     {
575         if (--pos->counter) {
576             continue;
577         }
578
579         pos->callback ? pos->callback(pos->payload) : 1;
580
581         if (pos->flags & TIMER_MODE_PERIODIC) {
582             pos->counter = pos->deadline;
583         } else {
584             llist_delete(&pos->link);
585             lxfree(pos);
586         }
587     }
588 }
589 ```
590
591 ## 参考
592
593 [1]intel-500-pch, 31.1 RTC Indexed Registers Summary
594
595 [2]mc146818a(cmos-rtc), Table 5
596
597 [3]https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html?highlight=rsdp#root-system-description-pointer-rsdp-structure
598
599 [4]Intel Manual,Vol 3A, 10-13, Figure 10-8. Local Vector Table (LVT)
600
601 [5]Intel Manual, Vol. 3A, 10-17, Figure 10-10. Divide Configuration Register
602
603 [6]Intel Manual, Vol. 3A, 10-16, 10.5.4 APIC Timer
604
605 [7]Intel Manual, Vol. 3A, 10-17, Figure 10-11. Initial Count and Current Count Registers
606
607 [8]intel-500-pch, p12, INTERRUPTS