| 1 | #include <bootstage.h> |
| 2 | #include <bootstage_condition.h> |
| 3 | #include <console/printf.h> |
| 4 | #include <global.h> |
| 5 | #include <log.h> |
| 6 | #include <rw_once.h> |
| 7 | #include <string.h> |
| 8 | |
| 9 | /* The standard bootstage.h API and the fancy condition stuff are both here */ |
| 10 | |
| 11 | static LOG_HANDLE_DECLARE_DEFAULT(bootstage); |
| 12 | const char *bootstage_str[BOOTSTAGE_COUNT] = { |
| 13 | [BOOTSTAGE_NONE] = "None" , |
| 14 | [BOOTSTAGE_EARLY_FB] = "Early - Framebuffer" , |
| 15 | [BOOTSTAGE_EARLY_ALLOCATORS] = "Early - Allocators" , |
| 16 | [BOOTSTAGE_EARLY_DEVICES] = "Early - Devices" , |
| 17 | [BOOTSTAGE_MID_MP] = "Mid - SMP" , |
| 18 | [BOOTSTAGE_MID_TOPOLOGY] = "Mid - Topology" , |
| 19 | [BOOTSTAGE_MID_ALLOCATORS] = "Mid - Allocators" , |
| 20 | [BOOTSTAGE_LATE] = "Late" , |
| 21 | [BOOTSTAGE_COMPLETE] = "Complete" , |
| 22 | }; |
| 23 | |
| 24 | enum bootstage bootstage_get() { |
| 25 | return global.current_bootstage; |
| 26 | } |
| 27 | |
| 28 | static void bootstage_write_jump(struct bootstage_condition_entry *ent) { |
| 29 | vaddr_t vjump = (vaddr_t) ent->code; |
| 30 | vaddr_t vdest = (vaddr_t) ent->target; |
| 31 | vaddr_t rel32ptr = vjump + 1; /* A byte over */ |
| 32 | uint32_t rel32 = vdest - vjump - 5; /* For the jump insn */ |
| 33 | |
| 34 | /* Write 0xE9 to the jump place, then the rel32 dest one byte over LE */ |
| 35 | WRITE_ONCE(*(uint8_t *) ent->code, (uint8_t) 0xE9); |
| 36 | WRITE_ONCE(*(uint32_t *) rel32ptr, (uint32_t) rel32); |
| 37 | } |
| 38 | |
| 39 | static void bootstage_write_nop(struct bootstage_condition_entry *ent) { |
| 40 | uint8_t *vjump = ent->code; |
| 41 | |
| 42 | /* ingenuity */ |
| 43 | const uint8_t nop5[5] = {0x0F, 0x1F, 0x44, 0x00, 0x00}; |
| 44 | memcpy(vjump, nop5, 5); |
| 45 | } |
| 46 | |
| 47 | static bool bootstage_taken(struct bootstage_condition_entry *ent, |
| 48 | enum bootstage bs) { |
| 49 | enum bootstage ebs = ent->stage; |
| 50 | enum bootstage_condition cond = ent->cond; |
| 51 | switch (cond) { |
| 52 | case BOOTSTAGE_CONDITION_EQ: return bs == ebs; |
| 53 | case BOOTSTAGE_CONDITION_LE: return bs <= ebs; |
| 54 | case BOOTSTAGE_CONDITION_LT: return bs < ebs; |
| 55 | case BOOTSTAGE_CONDITION_GE: return bs >= ebs; |
| 56 | case BOOTSTAGE_CONDITION_GT: return bs > ebs; |
| 57 | default: kassert_unreachable("invalid bootstage_condition_entry condition" ); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | static void bootstage_patch_all(enum bootstage bs) { |
| 62 | struct bootstage_condition_entry *ent; |
| 63 | linker_section_for_each_object(ent, bootstage_condition_entries) { |
| 64 | if (bootstage_taken(ent, bs)) { |
| 65 | bootstage_write_jump(ent); |
| 66 | } else { |
| 67 | bootstage_write_nop(ent); |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | void bootstage_advance(enum bootstage new) { |
| 73 | /* disable interrupts to be safe */ |
| 74 | bool ints = are_interrupts_enabled(); |
| 75 | disable_interrupts(); |
| 76 | |
| 77 | global.current_bootstage = new; |
| 78 | atomic_thread_fence(memory_order_seq_cst); |
| 79 | |
| 80 | /* EARLY_FB leaves the kernel RO, we don't have patchability */ |
| 81 | if (new > BOOTSTAGE_EARLY_FB) |
| 82 | bootstage_patch_all(bs: new); |
| 83 | |
| 84 | if (ints) |
| 85 | enable_interrupts(); |
| 86 | |
| 87 | log_info_global(LOG_HANDLE(bootstage), "Reached bootstage \'%s\'" , |
| 88 | bootstage_str[new]); |
| 89 | } |
| 90 | |