]>
Commit | Line | Data |
---|---|---|
f427ee49 A |
1 | /* |
2 | * Copyright (c) 2019 Apple Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
14 | * | |
15 | * Please obtain a copy of the License at | |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
25 | * | |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ | |
27 | */ | |
28 | /*! | |
29 | * ARM64-specific functions required to support hibernation exit. | |
30 | */ | |
31 | ||
32 | #include <mach/mach_types.h> | |
33 | #include <kern/misc_protos.h> | |
34 | #include <IOKit/IOHibernatePrivate.h> | |
35 | #include <machine/pal_hibernate.h> | |
36 | #include <pexpert/arm/dockchannel.h> | |
37 | #include <ptrauth.h> | |
38 | #include <arm/cpu_data_internal.h> | |
39 | #include <arm/cpu_internal.h> | |
2a1bd2d3 | 40 | #include <libkern/section_keywords.h> |
f427ee49 | 41 | |
f427ee49 A |
42 | |
43 | pal_hib_tramp_result_t gHibTramp; | |
2a1bd2d3 A |
44 | pal_hib_globals_t gHibernateGlobals MARK_AS_HIBERNATE_DATA_CONST_LATE; |
45 | ||
46 | // as a workaround for <rdar://problem/70121432> References between different compile units in xnu shouldn't go through GOT | |
47 | // all of the extern symbols that we refer to in this file have to be declared with hidden visibility | |
48 | extern IOHibernateImageHeader *gIOHibernateCurrentHeader __attribute__((visibility("hidden"))); | |
49 | extern const uint32_t ccsha256_initial_state[8] __attribute__((visibility("hidden"))); | |
50 | extern void AccelerateCrypto_SHA256_compress(ccdigest_state_t state, size_t numBlocks, const void *data) __attribute__((visibility("hidden"))); | |
51 | extern void ccdigest_final_64be(const struct ccdigest_info *di, ccdigest_ctx_t, unsigned char *digest) __attribute__((visibility("hidden"))); | |
52 | extern struct pmap_cpu_data_array_entry pmap_cpu_data_array[MAX_CPUS] __attribute__((visibility("hidden"))); | |
53 | extern bool hib_entry_pmap_lockdown __attribute__((visibility("hidden"))); | |
f427ee49 A |
54 | |
55 | uintptr_t | |
56 | hibernate_restore_phys_page(uint64_t src, uint64_t dst, uint32_t len, __unused uint32_t procFlags) | |
57 | { | |
58 | void *d = (void*)pal_hib_map(DEST_COPY_AREA, dst); | |
59 | __nosan_memcpy(d, (void*)src, len); | |
60 | return (uintptr_t)d; | |
61 | } | |
62 | ||
63 | uintptr_t | |
64 | pal_hib_map(pal_hib_map_type_t virt, uint64_t phys) | |
65 | { | |
66 | switch (virt) { | |
67 | case DEST_COPY_AREA: | |
68 | case COPY_PAGE_AREA: | |
69 | case SCRATCH_AREA: | |
70 | case WKDM_AREA: | |
71 | return phys + gHibTramp.memSlide; | |
72 | case BITMAP_AREA: | |
73 | case IMAGE_AREA: | |
74 | case IMAGE2_AREA: | |
75 | return phys; | |
76 | default: | |
77 | HIB_ASSERT(0); | |
78 | } | |
79 | } | |
80 | ||
81 | void | |
82 | pal_hib_restore_pal_state(__unused uint32_t *arg) | |
83 | { | |
84 | } | |
85 | ||
86 | void | |
87 | pal_hib_resume_init(pal_hib_ctx_t *ctx, hibernate_page_list_t *map, uint32_t *nextFree) | |
88 | { | |
f427ee49 A |
89 | } |
90 | ||
91 | void | |
92 | pal_hib_restored_page(pal_hib_ctx_t *ctx, pal_hib_restore_stage_t stage, ppnum_t ppnum) | |
93 | { | |
f427ee49 A |
94 | } |
95 | ||
96 | void | |
97 | pal_hib_patchup(pal_hib_ctx_t *ctx) | |
98 | { | |
f427ee49 | 99 | |
c3c9b80d A |
100 | /* Reinit the ppl hib lock as it was saved to the hibernation image held. */ |
101 | ppl_hib_lock_reinit(); | |
102 | ||
f427ee49 | 103 | // DRAM pages are captured from a PPL context, so here we restore all cpu_data structures to a non-PPL context |
f427ee49 A |
104 | for (int i = 0; i < MAX_CPUS; i++) { |
105 | pmap_cpu_data_array[i].cpu_data.ppl_state = PPL_STATE_KERNEL; | |
106 | pmap_cpu_data_array[i].cpu_data.ppl_kern_saved_sp = 0; | |
107 | } | |
108 | ||
109 | // cluster CTRR state needs to be reconfigured | |
110 | init_ctrr_cluster_states(); | |
111 | ||
112 | // Calls into the pmap that could potentially modify pmap data structures | |
113 | // during image copying were explicitly blocked on hibernation entry. | |
114 | // Resetting this variable to false allows those calls to be made again. | |
f427ee49 A |
115 | hib_entry_pmap_lockdown = false; |
116 | } | |
117 | ||
118 | void | |
119 | pal_hib_decompress_page(void *src, void *dst, void *scratch, unsigned int compressedSize) | |
120 | { | |
121 | const void *wkdmSrc; | |
122 | if (((uint64_t)src) & 63) { | |
123 | // the wkdm instruction requires that our source buffer be aligned, so copy into an aligned buffer if necessary | |
124 | __nosan_memcpy(scratch, src, compressedSize); | |
125 | wkdmSrc = scratch; | |
126 | } else { | |
127 | wkdmSrc = src; | |
128 | } | |
129 | HIB_ASSERT((((uint64_t)wkdmSrc) & 63) == 0); | |
130 | HIB_ASSERT((((uint64_t)dst) & PAGE_MASK) == 0); | |
131 | struct { | |
132 | uint32_t reserved:12; | |
133 | uint32_t status:3; | |
134 | uint32_t reserved2:17; | |
135 | uint32_t popcnt:18; | |
136 | uint32_t reserved3:14; | |
137 | } result = { .status = ~0u }; | |
138 | __asm__ volatile ("wkdmd %0, %1" : "=r"(result): "r"(dst), "0"(wkdmSrc)); | |
139 | HIB_ASSERT(result.status == 0); | |
140 | } | |
141 | ||
142 | // proc_reg's ARM_TTE_TABLE_NS has both NSTABLE and NS set | |
143 | #define ARM_LPAE_NSTABLE 0x8000000000000000ULL | |
144 | ||
145 | #define TOP_LEVEL 1 | |
146 | #define LAST_TABLE_LEVEL 3 | |
147 | #define PAGE_GRANULE_SHIFT 14 | |
148 | #define PAGE_GRANULE_SIZE ((size_t)1<<PAGE_GRANULE_SHIFT) | |
149 | #define PAGE_GRANULE_MASK (PAGE_GRANULE_SIZE-1) | |
150 | #define LEVEL_SHIFT(level) (47 - (level * 11)) | |
151 | ||
152 | #define PTE_EMPTY(ent) ((ent) == 0) | |
153 | ||
154 | typedef struct { | |
155 | hibernate_page_list_t *bitmap; | |
156 | uint32_t nextFree; | |
157 | uint64_t page_table_base; | |
158 | } map_ctx; | |
159 | ||
160 | static void | |
161 | hib_bzero(volatile void *s, size_t n) | |
162 | { | |
163 | // can't use __nosan_bzero while the MMU is off, so do it manually | |
164 | while (n > sizeof(uint64_t)) { | |
165 | *(volatile uint64_t *)s = 0; | |
166 | s += sizeof(uint64_t); | |
167 | n -= sizeof(uint64_t); | |
168 | } | |
169 | while (n > sizeof(uint32_t)) { | |
170 | *(volatile uint32_t *)s = 0; | |
171 | s += sizeof(uint32_t); | |
172 | n -= sizeof(uint32_t); | |
173 | } | |
174 | while (n) { | |
175 | *(volatile char *)s = 0; | |
176 | s++; | |
177 | n--; | |
178 | } | |
179 | } | |
180 | ||
181 | static uint64_t | |
182 | allocate_page(map_ctx *ctx) | |
183 | { | |
184 | // pages that were unnecessary for preservation when we entered hibernation are | |
185 | // marked as free in ctx->bitmap, so they are available for scratch usage during | |
186 | // resume; here, we "borrow" one of these free pages to use as part of our temporary | |
187 | // page tables | |
188 | ppnum_t ppnum = hibernate_page_list_grab(ctx->bitmap, &ctx->nextFree); | |
189 | hibernate_page_bitset(ctx->bitmap, FALSE, ppnum); | |
190 | uint64_t result = ptoa_64(ppnum); | |
191 | hib_bzero((void *)result, PAGE_SIZE); | |
192 | return result; | |
193 | } | |
194 | ||
195 | static void | |
196 | create_map_entries(map_ctx *ctx, uint64_t vaddr, uint64_t paddr, uint64_t size, uint64_t map_flags) | |
197 | { | |
198 | // if we've set gHibTramp.memSlide, we should already be running with the MMU on; | |
199 | // in this case, we don't permit further modification to the page table | |
200 | HIB_ASSERT(!gHibTramp.memSlide); | |
201 | ||
202 | int level = TOP_LEVEL; | |
203 | volatile uint64_t *table_base = (uint64_t *)ctx->page_table_base; | |
204 | if (map_flags == 0) { | |
205 | paddr = 0; // no physical address for none mappings | |
206 | } | |
207 | ||
208 | while (size) { | |
209 | HIB_ASSERT(level >= 1); | |
210 | HIB_ASSERT(level <= LAST_TABLE_LEVEL); | |
211 | ||
212 | size_t level_shift = LEVEL_SHIFT(level); | |
213 | size_t level_entries = PAGE_GRANULE_SIZE / sizeof(uint64_t); | |
214 | size_t level_size = 1ull << level_shift; | |
215 | size_t level_mask = level_size - 1; | |
216 | size_t index = (vaddr >> level_shift) & (level_entries - 1); | |
217 | // Can we make block entries here? Must be permitted at this | |
218 | // level, have enough bytes remaining, and both virtual and | |
219 | // physical addresses aligned to a block. | |
220 | if ((level >= 2) && | |
221 | size >= level_size && | |
222 | ((vaddr | paddr) & level_mask) == 0) { | |
223 | // Map contiguous blocks. | |
224 | size_t num_entries = MIN(size / level_size, level_entries - index); | |
225 | if (map_flags) { | |
226 | uint64_t entry = map_flags | ((level < LAST_TABLE_LEVEL) ? ARM_TTE_TYPE_BLOCK : ARM_TTE_TYPE_L3BLOCK); | |
227 | for (size_t i = 0; i < num_entries; i++) { | |
228 | HIB_ASSERT(PTE_EMPTY(table_base[index + i])); | |
229 | table_base[index + i] = entry | paddr; | |
230 | paddr += level_size; | |
231 | } | |
232 | } else { | |
233 | // make sure all the corresponding entries are empty | |
234 | for (size_t i = 0; i < num_entries; i++) { | |
235 | HIB_ASSERT(PTE_EMPTY(table_base[index + i])); | |
236 | } | |
237 | } | |
238 | size_t mapped = num_entries * level_size; | |
239 | size -= mapped; | |
240 | if (size) { | |
241 | // map the remaining at the top level | |
242 | level = TOP_LEVEL; | |
243 | table_base = (uint64_t *)ctx->page_table_base; | |
244 | vaddr += mapped; | |
245 | // paddr already incremented above if necessary | |
246 | } | |
247 | } else { | |
248 | // Sub-divide into a next level table. | |
249 | HIB_ASSERT(level < LAST_TABLE_LEVEL); | |
250 | uint64_t entry = table_base[index]; | |
251 | HIB_ASSERT((entry & (ARM_TTE_VALID | ARM_TTE_TYPE_MASK)) != (ARM_TTE_VALID | ARM_TTE_TYPE_BLOCK)); // Breaking down blocks not implemented | |
252 | uint64_t sub_base = entry & ARM_TTE_TABLE_MASK; | |
253 | if (!sub_base) { | |
254 | sub_base = allocate_page(ctx); | |
255 | HIB_ASSERT((sub_base & PAGE_GRANULE_MASK) == 0); | |
256 | table_base[index] = sub_base | ARM_LPAE_NSTABLE | ARM_TTE_TYPE_TABLE | ARM_TTE_VALID; | |
257 | } | |
258 | // map into the sub table | |
259 | level++; | |
260 | table_base = (uint64_t *)sub_base; | |
261 | } | |
262 | } | |
263 | } | |
264 | ||
265 | static void | |
266 | map_range_start_end(map_ctx *ctx, uint64_t start, uint64_t end, uint64_t slide, uint64_t flags) | |
267 | { | |
268 | HIB_ASSERT(end >= start); | |
269 | create_map_entries(ctx, start + slide, start, end - start, flags); | |
270 | } | |
271 | ||
272 | #define MAP_FLAGS_COMMON (ARM_PTE_AF | ARM_PTE_NS | ARM_TTE_VALID | ARM_PTE_SH(SH_OUTER_MEMORY) | ARM_PTE_ATTRINDX(CACHE_ATTRINDX_WRITEBACK)) | |
273 | #define MAP_DEVICE (ARM_PTE_AF | ARM_TTE_VALID | ARM_PTE_PNX | ARM_PTE_NX | ARM_PTE_SH(SH_NONE) | ARM_PTE_ATTRINDX(CACHE_ATTRINDX_DISABLE)) | |
274 | #define MAP_RO (MAP_FLAGS_COMMON | ARM_PTE_PNX | ARM_PTE_NX | ARM_PTE_AP(AP_RONA)) | |
275 | #define MAP_RW (MAP_FLAGS_COMMON | ARM_PTE_PNX | ARM_PTE_NX) | |
276 | #define MAP_RX (MAP_FLAGS_COMMON | ARM_PTE_AP(AP_RONA)) | |
277 | ||
278 | static void | |
279 | map_register_page(map_ctx *ctx, vm_address_t regPage) | |
280 | { | |
281 | uint64_t regBase = trunc_page(regPage); | |
282 | if (regBase) { | |
283 | map_range_start_end(ctx, regBase, regBase + PAGE_SIZE, 0, MAP_DEVICE); | |
284 | } | |
285 | } | |
286 | ||
287 | static void | |
288 | iterate_bitmaps(const map_ctx *ctx, bool (^callback)(const hibernate_bitmap_t *bank_bitmap)) | |
289 | { | |
290 | hibernate_bitmap_t *bank_bitmap = &ctx->bitmap->bank_bitmap[0]; | |
291 | for (uint32_t bank = 0; bank < ctx->bitmap->bank_count; bank++) { | |
292 | if (!callback(bank_bitmap)) { | |
293 | return; | |
294 | } | |
295 | bank_bitmap = (hibernate_bitmap_t*)&bank_bitmap->bitmap[bank_bitmap->bitmapwords]; | |
296 | } | |
297 | } | |
298 | ||
299 | // during hibernation resume, we can't use the original kernel page table (because we don't know what it was), so we instead | |
300 | // create a temporary page table to use during hibernation resume; since the original kernel page table was part of DRAM, | |
301 | // it will be restored by the time we're done with hibernation resume, at which point we can jump through the reset vector | |
302 | // to reload the original page table | |
303 | void | |
304 | pal_hib_resume_tramp(uint32_t headerPpnum) | |
305 | { | |
306 | uint64_t header_phys = ptoa_64(headerPpnum); | |
307 | IOHibernateImageHeader *header = (IOHibernateImageHeader *)header_phys; | |
308 | IOHibernateHibSegInfo *seg_info = &header->hibSegInfo; | |
309 | uint64_t hib_text_start = ptoa_64(header->restore1CodePhysPage); | |
310 | ||
311 | __block map_ctx ctx = {}; | |
312 | uint64_t map_phys = header_phys | |
313 | + (offsetof(IOHibernateImageHeader, fileExtentMap) | |
314 | + header->fileExtentMapSize | |
315 | + ptoa_32(header->restore1PageCount) | |
316 | + header->previewSize); | |
317 | ctx.bitmap = (hibernate_page_list_t *)map_phys; | |
318 | ||
319 | // find the bank describing xnu's map | |
320 | __block uint64_t phys_start = 0, phys_end = 0; | |
321 | iterate_bitmaps(&ctx, ^bool (const hibernate_bitmap_t *bank_bitmap) { | |
322 | if ((bank_bitmap->first_page <= header->restore1CodePhysPage) && | |
323 | (bank_bitmap->last_page >= header->restore1CodePhysPage)) { | |
324 | phys_start = ptoa_64(bank_bitmap->first_page); | |
325 | phys_end = ptoa_64(bank_bitmap->last_page) + PAGE_SIZE; | |
326 | return false; | |
327 | } | |
328 | return true; | |
329 | }); | |
330 | ||
331 | HIB_ASSERT(phys_start != 0); | |
332 | HIB_ASSERT(phys_end != 0); | |
333 | ||
334 | hib_bzero(&gHibTramp, sizeof(gHibTramp)); | |
f427ee49 A |
335 | |
336 | // During hibernation resume, we create temporary mappings that do not collide with where any of the kernel mappings were originally. | |
337 | // Technically, non-collision isn't a requirement, but doing this means that if some code accidentally jumps to a VA in the original | |
338 | // kernel map, it won't be present in our temporary map and we'll get an exception when jumping to an unmapped address. | |
339 | // The base address of our temporary mappings is adjusted by a random amount as a "poor-man's ASLR". We don’t have a good source of random | |
340 | // numbers in this context, so we just use some of the bits from one of imageHeaderHMMAC, which should be random enough. | |
341 | uint16_t rand = (uint16_t)(((header->imageHeaderHMAC[0]) << 8) | header->imageHeaderHMAC[1]); | |
2a1bd2d3 | 342 | uint64_t mem_slide = gHibernateGlobals.kernelSlide - (phys_end - phys_start) * 4 - rand * 256 * PAGE_SIZE; |
f427ee49 A |
343 | |
344 | // make sure we don't clobber any of the pages we need for restore | |
345 | hibernate_reserve_restore_pages(header_phys, header, ctx.bitmap); | |
346 | ||
347 | // init nextFree | |
348 | hibernate_page_list_grab(ctx.bitmap, &ctx.nextFree); | |
349 | ||
350 | // map ttbr1 pages | |
351 | ctx.page_table_base = allocate_page(&ctx); | |
352 | gHibTramp.ttbr1 = ctx.page_table_base; | |
353 | ||
354 | uint64_t first_seg_start = 0, last_seg_end = 0, hib_text_end = 0; | |
355 | for (size_t i = 0; i < NUM_HIBSEGINFO_SEGMENTS; i++) { | |
356 | uint64_t size = ptoa_64(seg_info->segments[i].pageCount); | |
357 | if (size) { | |
358 | uint64_t seg_start = ptoa_64(seg_info->segments[i].physPage); | |
359 | uint64_t seg_end = seg_start + size; | |
360 | uint32_t protection = seg_info->segments[i].protection; | |
361 | if (protection != VM_PROT_NONE) { | |
362 | // make sure the segment is in bounds | |
363 | HIB_ASSERT(seg_start >= phys_start); | |
364 | HIB_ASSERT(seg_end <= phys_end); | |
365 | ||
366 | if (!first_seg_start) { | |
367 | first_seg_start = seg_start; | |
368 | } | |
369 | if (last_seg_end) { | |
370 | // map the "hole" as RW | |
371 | map_range_start_end(&ctx, last_seg_end, seg_start, mem_slide, MAP_RW); | |
372 | } | |
373 | // map the segments described in machine_header at their original locations | |
374 | bool executable = (protection & VM_PROT_EXECUTE); | |
375 | bool writeable = (protection & VM_PROT_WRITE); | |
376 | uint64_t map_flags = executable ? MAP_RX : writeable ? MAP_RW : MAP_RO; | |
2a1bd2d3 | 377 | map_range_start_end(&ctx, seg_start, seg_end, gHibernateGlobals.kernelSlide, map_flags); |
f427ee49 A |
378 | last_seg_end = seg_end; |
379 | } | |
380 | if (seg_info->segments[i].physPage == header->restore1CodePhysPage) { | |
381 | // this is the hibtext segment, so remember where it ends | |
382 | hib_text_end = seg_end; | |
383 | } | |
384 | } | |
385 | } | |
386 | // map the rest of kernel memory (the pages that come before and after our segments) as RW | |
387 | map_range_start_end(&ctx, phys_start, first_seg_start, mem_slide, MAP_RW); | |
388 | map_range_start_end(&ctx, last_seg_end, phys_end, mem_slide, MAP_RW); | |
389 | ||
390 | // map all of the remaining banks that we didn't already deal with | |
391 | iterate_bitmaps(&ctx, ^bool (const hibernate_bitmap_t *bank_bitmap) { | |
392 | uint64_t bank_start = ptoa_64(bank_bitmap->first_page); | |
393 | uint64_t bank_end = ptoa_64(bank_bitmap->last_page) + PAGE_SIZE; | |
394 | if (bank_start == phys_start) { | |
395 | // skip this bank since we already covered it above | |
396 | } else { | |
397 | // map the bank RW | |
398 | map_range_start_end(&ctx, bank_start, bank_end, mem_slide, MAP_RW); | |
399 | } | |
400 | return true; | |
401 | }); | |
402 | ||
403 | // map ttbr0 pages | |
404 | ctx.page_table_base = allocate_page(&ctx); | |
405 | gHibTramp.ttbr0 = ctx.page_table_base; | |
406 | ||
407 | // map hib text P=V so that we can still execute at its physical address | |
408 | map_range_start_end(&ctx, hib_text_start, hib_text_end, 0, MAP_RX); | |
409 | ||
410 | // map the hib image P=V, RW | |
411 | uint64_t image_start = trunc_page(header_phys); | |
412 | uint64_t image_end = round_page(header_phys + header->image1Size); | |
413 | map_range_start_end(&ctx, image_start, image_end, 0, MAP_RW); | |
414 | ||
415 | // map the handoff pages P=V, RO | |
416 | image_start = ptoa_64(header->handoffPages); | |
417 | image_end = image_start + ptoa_64(header->handoffPageCount); | |
418 | map_range_start_end(&ctx, image_start, image_end, 0, MAP_RO); | |
419 | ||
420 | // map some device register pages | |
421 | if (gHibernateGlobals.dockChannelRegBase) { | |
422 | #define dockchannel_uart_base gHibernateGlobals.dockChannelRegBase | |
423 | vm_address_t dockChannelRegBase = trunc_page(&rDOCKCHANNELS_DEV_WSTAT(DOCKCHANNEL_UART_CHANNEL)); | |
424 | map_register_page(&ctx, dockChannelRegBase); | |
425 | } | |
426 | map_register_page(&ctx, gHibernateGlobals.hibUartRegBase); | |
427 | map_register_page(&ctx, gHibernateGlobals.hmacRegBase); | |
428 | ||
429 | gHibTramp.memSlide = mem_slide; | |
430 | } |