xref: /openbsd-src/sys/arch/i386/i386/hibernate_machdep.c (revision 1edb6294a762f6384eba7bcc9c4b5ac1e44b63cc)
1 /*	$OpenBSD: hibernate_machdep.c,v 1.62 2024/06/19 13:27:26 jsg Exp $	*/
2 
3 /*
4  * Copyright (c) 2011 Mike Larkin <mlarkin@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 #include <sys/systm.h>
21 #include <sys/device.h>
22 #include <sys/disk.h>
23 #include <sys/disklabel.h>
24 #include <sys/hibernate.h>
25 
26 #include <uvm/uvm_extern.h>
27 
28 #include <machine/biosvar.h>
29 #include <machine/hibernate.h>
30 #include <machine/kcore.h>
31 #include <machine/pmap.h>
32 
33 #ifdef MULTIPROCESSOR
34 #include <machine/mpbiosvar.h>
35 #endif /* MULTIPROCESSOR */
36 
37 #include <dev/acpi/acpivar.h>
38 
39 #include "acpi.h"
40 #include "wd.h"
41 #include "ahci.h"
42 #include "softraid.h"
43 #include "sd.h"
44 #include "sdmmc.h"
45 
46 /* Hibernate support */
47 void    hibernate_enter_resume_4k_pte(vaddr_t, paddr_t);
48 void    hibernate_enter_resume_4k_pde(vaddr_t);
49 void    hibernate_enter_resume_4m_pde(vaddr_t, paddr_t);
50 
51 extern	caddr_t start, end;
52 extern	int ndumpmem;
53 extern  struct dumpmem dumpmem[];
54 extern	bios_memmap_t *bios_memmap;
55 
56 /*
57  * Hibernate always uses non-PAE page tables during resume, so
58  * redefine masks and pt_entry_t sizes in case PAE is in use.
59  */
60 #define PAGE_MASK_L2    (NBPD - 1)
61 typedef uint32_t pt_entry_t;
62 
63 /*
64  * i386 MD Hibernate functions
65  *
66  * see i386 hibernate.h for lowmem layout used during hibernate
67  */
68 
69 /*
70  * Returns the hibernate write I/O function to use on this machine
71  */
72 hibio_fn
get_hibernate_io_function(dev_t dev)73 get_hibernate_io_function(dev_t dev)
74 {
75 	char *blkname = findblkname(major(dev));
76 
77 	if (blkname == NULL)
78 		return NULL;
79 
80 #if NWD > 0
81 	if (strcmp(blkname, "wd") == 0) {
82 		extern int wd_hibernate_io(dev_t dev, daddr_t blkno,
83 		    vaddr_t addr, size_t size, int op, void *page);
84 		return wd_hibernate_io;
85 	}
86 #endif
87 #if NSD > 0
88 	if (strcmp(blkname, "sd") == 0) {
89 		extern struct cfdriver sd_cd;
90 		extern int ahci_hibernate_io(dev_t dev, daddr_t blkno,
91 		    vaddr_t addr, size_t size, int op, void *page);
92 		extern int sr_hibernate_io(dev_t dev, daddr_t blkno,
93 		    vaddr_t addr, size_t size, int op, void *page);
94 		extern int sdmmc_scsi_hibernate_io(dev_t dev, daddr_t blkno,
95 		    vaddr_t addr, size_t size, int op, void *page);
96 		struct device *dv = disk_lookup(&sd_cd, DISKUNIT(dev));
97 		struct {
98 			const char *driver;
99 			hibio_fn io_func;
100 		} sd_io_funcs[] = {
101 #if NAHCI > 0
102 			{ "ahci", ahci_hibernate_io },
103 #endif
104 #if NSOFTRAID > 0
105 			{ "softraid", sr_hibernate_io },
106 #endif
107 #if NSDMMC > 0
108 			{ "sdmmc", sdmmc_scsi_hibernate_io },
109 #endif
110 		};
111 
112 		if (dv && dv->dv_parent && dv->dv_parent->dv_parent) {
113 			const char *driver = dv->dv_parent->dv_parent->dv_cfdata->
114 			    cf_driver->cd_name;
115 			int i;
116 
117 			for (i = 0; i < nitems(sd_io_funcs); i++) {
118 				if (strcmp(driver, sd_io_funcs[i].driver) == 0)
119 					return sd_io_funcs[i].io_func;
120 			}
121 		}
122 	}
123 #endif /* NSD > 0 */
124 	return NULL;
125 }
126 
127 /*
128  * Gather MD-specific data and store into hiber_info
129  */
130 int
get_hibernate_info_md(union hibernate_info * hiber_info)131 get_hibernate_info_md(union hibernate_info *hiber_info)
132 {
133 	int i;
134 	bios_memmap_t *bmp;
135 
136 	/* Calculate memory ranges */
137 	hiber_info->nranges = ndumpmem;
138 	hiber_info->image_size = 0;
139 
140 	for (i = 0; i < ndumpmem; i++) {
141 		hiber_info->ranges[i].base = dumpmem[i].start * PAGE_SIZE;
142 		hiber_info->ranges[i].end = dumpmem[i].end * PAGE_SIZE;
143 		hiber_info->image_size += hiber_info->ranges[i].end -
144 		    hiber_info->ranges[i].base;
145 	}
146 
147 	/* Record lowmem PTP page */
148 	if (hiber_info->nranges >= nitems(hiber_info->ranges))
149 		return (1);
150 	hiber_info->ranges[hiber_info->nranges].base = PTP0_PA;
151 	hiber_info->ranges[hiber_info->nranges].end =
152 	    hiber_info->ranges[hiber_info->nranges].base + PAGE_SIZE;
153 	hiber_info->image_size += PAGE_SIZE;
154 	hiber_info->nranges++;
155 
156 #if NACPI > 0
157 	/* Record ACPI trampoline code page */
158 	if (hiber_info->nranges >= nitems(hiber_info->ranges))
159 		return (1);
160 	hiber_info->ranges[hiber_info->nranges].base = ACPI_TRAMPOLINE;
161 	hiber_info->ranges[hiber_info->nranges].end =
162 	    hiber_info->ranges[hiber_info->nranges].base + PAGE_SIZE;
163 	hiber_info->image_size += PAGE_SIZE;
164 	hiber_info->nranges++;
165 
166 	/* Record ACPI trampoline data page */
167 	if (hiber_info->nranges >= nitems(hiber_info->ranges))
168 		return (1);
169 	hiber_info->ranges[hiber_info->nranges].base = ACPI_TRAMP_DATA;
170 	hiber_info->ranges[hiber_info->nranges].end =
171 	    hiber_info->ranges[hiber_info->nranges].base + PAGE_SIZE;
172 	hiber_info->image_size += PAGE_SIZE;
173 	hiber_info->nranges++;
174 #endif
175 #ifdef MULTIPROCESSOR
176 	/* Record MP trampoline code page */
177 	if (hiber_info->nranges >= nitems(hiber_info->ranges))
178 		return (1);
179 	hiber_info->ranges[hiber_info->nranges].base = MP_TRAMPOLINE;
180 	hiber_info->ranges[hiber_info->nranges].end =
181 	    hiber_info->ranges[hiber_info->nranges].base + PAGE_SIZE;
182 	hiber_info->image_size += PAGE_SIZE;
183 	hiber_info->nranges++;
184 
185 	/* Record MP trampoline data page */
186 	if (hiber_info->nranges >= nitems(hiber_info->ranges))
187 		return (1);
188 	hiber_info->ranges[hiber_info->nranges].base = MP_TRAMP_DATA;
189 	hiber_info->ranges[hiber_info->nranges].end =
190 	    hiber_info->ranges[hiber_info->nranges].base + PAGE_SIZE;
191 	hiber_info->image_size += PAGE_SIZE;
192 	hiber_info->nranges++;
193 #endif /* MULTIPROCESSOR */
194 
195 	for (bmp = bios_memmap; bmp->type != BIOS_MAP_END; bmp++) {
196 		/* Skip non-NVS ranges (already processed) */
197 		if (bmp->type != BIOS_MAP_NVS)
198 			continue;
199 		if (hiber_info->nranges >= nitems(hiber_info->ranges))
200 			return (1);
201 
202 		i = hiber_info->nranges;
203 		hiber_info->ranges[i].base = round_page(bmp->addr);
204 		hiber_info->ranges[i].end = trunc_page(bmp->addr + bmp->size);
205 		hiber_info->image_size += hiber_info->ranges[i].end -
206 			hiber_info->ranges[i].base;
207 		hiber_info->nranges++;
208 	}
209 
210 	hibernate_sort_ranges(hiber_info);
211 
212 	return (0);
213 }
214 
215 /*
216  * Enter a mapping for va->pa in the resume pagetable, using
217  * the specified size.
218  *
219  * size : 0 if a 4KB mapping is desired
220  *        1 if a 4MB mapping is desired
221  */
222 void
hibernate_enter_resume_mapping(vaddr_t va,paddr_t pa,int size)223 hibernate_enter_resume_mapping(vaddr_t va, paddr_t pa, int size)
224 {
225 	if (size)
226 		return hibernate_enter_resume_4m_pde(va, pa);
227 	else
228 		return hibernate_enter_resume_4k_pte(va, pa);
229 }
230 
231 /*
232  * Enter a 4MB PDE mapping for the supplied VA/PA into the resume-time pmap
233  */
234 void
hibernate_enter_resume_4m_pde(vaddr_t va,paddr_t pa)235 hibernate_enter_resume_4m_pde(vaddr_t va, paddr_t pa)
236 {
237 	pt_entry_t *pde, npde;
238 
239 	pde = s4pde_4m(va);
240 	npde = (pa & HIB_PD_MASK) | PG_RW | PG_V | PG_M | PG_PS;
241 	*pde = npde;
242 }
243 
244 /*
245  * Enter a 4KB PTE mapping for the supplied VA/PA into the resume-time pmap.
246  */
247 void
hibernate_enter_resume_4k_pte(vaddr_t va,paddr_t pa)248 hibernate_enter_resume_4k_pte(vaddr_t va, paddr_t pa)
249 {
250 	pt_entry_t *pte, npte;
251 
252 	pte = s4pte_4k(va);
253 	npte = (pa & PMAP_PA_MASK) | PG_RW | PG_V | PG_M;
254 	*pte = npte;
255 }
256 
257 /*
258  * Enter a 4KB PDE mapping for the supplied VA into the resume-time pmap.
259  */
260 void
hibernate_enter_resume_4k_pde(vaddr_t va)261 hibernate_enter_resume_4k_pde(vaddr_t va)
262 {
263 	pt_entry_t *pde, npde;
264 
265 	pde = s4pde_4k(va);
266 	npde = (HIBERNATE_PT_PAGE & PMAP_PA_MASK) | PG_RW | PG_V | PG_M;
267 	*pde = npde;
268 }
269 
270 /*
271  * Create the resume-time page table. This table maps the image(pig) area,
272  * the kernel text area, and various utility pages for use during resume,
273  * since we cannot overwrite the resuming kernel's page table during inflate
274  * and expect things to work properly.
275  */
276 void
hibernate_populate_resume_pt(union hibernate_info * hib_info,paddr_t image_start,paddr_t image_end)277 hibernate_populate_resume_pt(union hibernate_info *hib_info,
278     paddr_t image_start, paddr_t image_end)
279 {
280 	int phys_page_number, i;
281 	paddr_t pa;
282 	vaddr_t kern_start_4m_va, kern_end_4m_va, page;
283 	vaddr_t piglet_start_va, piglet_end_va;
284 	struct pmap *kpm = pmap_kernel();
285 
286 	/* Identity map PD, PT, and stack pages */
287 	pmap_kenter_pa(HIBERNATE_PT_PAGE, HIBERNATE_PT_PAGE, PROT_MASK);
288 	pmap_kenter_pa(HIBERNATE_PD_PAGE, HIBERNATE_PD_PAGE, PROT_MASK);
289 	pmap_kenter_pa(HIBERNATE_STACK_PAGE, HIBERNATE_STACK_PAGE, PROT_MASK);
290 	pmap_activate(curproc);
291 
292 	bzero((caddr_t)HIBERNATE_PT_PAGE, PAGE_SIZE);
293 	bzero((caddr_t)HIBERNATE_PD_PAGE, PAGE_SIZE);
294 	bzero((caddr_t)HIBERNATE_STACK_PAGE, PAGE_SIZE);
295 
296 	/* PDE for low pages */
297 	hibernate_enter_resume_4k_pde(0);
298 
299 	/*
300 	 * Identity map low physical pages.
301 	 * See arch/i386/include/hibernate_var.h for page ranges used here.
302 	 */
303 	for (i = ACPI_TRAMPOLINE; i <= HIBERNATE_HIBALLOC_PAGE; i += PAGE_SIZE)
304 		hibernate_enter_resume_mapping(i, i, 0);
305 
306 	/*
307 	 * Map current kernel VA range using 4M pages
308 	 */
309 	kern_start_4m_va = (vaddr_t)&start & ~(PAGE_MASK_L2);
310 	kern_end_4m_va = (vaddr_t)&end & ~(PAGE_MASK_L2);
311 
312 	/* i386 kernels load at 2MB phys (on the 0th 4mb page) */
313 	phys_page_number = 0;
314 
315 	for (page = kern_start_4m_va; page <= kern_end_4m_va;
316 	    page += NBPD, phys_page_number++) {
317 		pa = (paddr_t)(phys_page_number * NBPD);
318 		hibernate_enter_resume_mapping(page, pa, 1);
319 	}
320 
321 	/*
322 	 * Identity map the image (pig) area
323 	 */
324 	phys_page_number = image_start / NBPD;
325 	image_start &= ~(PAGE_MASK_L2);
326 	image_end &= ~(PAGE_MASK_L2);
327 	for (page = image_start; page <= image_end ;
328 	    page += NBPD, phys_page_number++) {
329 		pa = (paddr_t)(phys_page_number * NBPD);
330 		hibernate_enter_resume_mapping(page, pa, 1);
331 	}
332 
333 	/*
334 	 * Identity map the piglet using 4MB pages.
335 	 */
336 	phys_page_number = hib_info->piglet_pa / NBPD;
337 
338 	/* VA == PA */
339 	piglet_start_va = hib_info->piglet_pa;
340 	piglet_end_va = piglet_start_va + HIBERNATE_CHUNK_SIZE * 4;
341 
342 	for (page = piglet_start_va; page <= piglet_end_va;
343 	    page += NBPD, phys_page_number++) {
344 		pa = (paddr_t)(phys_page_number * NBPD);
345 		hibernate_enter_resume_mapping(page, pa, 1);
346 	}
347 
348 	/*
349 	 * Fill last 8 slots of the new PD with the PAE PDPTEs of the
350 	 * kernel pmap, such that we can easily switch back into
351 	 * non-PAE mode.  If we're running in non-PAE mode, this will
352 	 * just fill the slots with zeroes.
353 	 */
354 	((uint64_t *)HIBERNATE_PD_PAGE)[508] = kpm->pm_pdidx[0];
355 	((uint64_t *)HIBERNATE_PD_PAGE)[509] = kpm->pm_pdidx[1];
356 	((uint64_t *)HIBERNATE_PD_PAGE)[510] = kpm->pm_pdidx[2];
357 	((uint64_t *)HIBERNATE_PD_PAGE)[511] = kpm->pm_pdidx[3];
358 
359 	/* Unmap MMU pages (stack remains mapped) */
360 	pmap_kremove(HIBERNATE_PT_PAGE, PAGE_SIZE);
361 	pmap_kremove(HIBERNATE_PD_PAGE, PAGE_SIZE);
362 	pmap_activate(curproc);
363 }
364 
365 /*
366  * During inflate, certain pages that contain our bookkeeping information
367  * (eg, the chunk table, scratch pages, retguard region, etc) need to be
368  * skipped over and not inflated into.
369  *
370  * Return values:
371  *  HIB_MOVE: if the physical page at dest should be moved to the retguard save
372  *   region in the piglet
373  *  HIB_SKIP: if the physical page at dest should be skipped
374  *  0: otherwise (no special treatment needed)
375  */
376 int
hibernate_inflate_skip(union hibernate_info * hib_info,paddr_t dest)377 hibernate_inflate_skip(union hibernate_info *hib_info, paddr_t dest)
378 {
379 	extern paddr_t retguard_start_phys, retguard_end_phys;
380 
381 	if (dest >= hib_info->piglet_pa &&
382 	    dest <= (hib_info->piglet_pa + 4 * HIBERNATE_CHUNK_SIZE))
383 		return (HIB_SKIP);
384 
385 	if (dest >= retguard_start_phys && dest <= retguard_end_phys)
386 		return (HIB_MOVE);
387 
388 	return (0);
389 }
390 
391 void
hibernate_enable_intr_machdep(void)392 hibernate_enable_intr_machdep(void)
393 {
394 	intr_enable();
395 }
396 
397 void
hibernate_disable_intr_machdep(void)398 hibernate_disable_intr_machdep(void)
399 {
400 	intr_disable();
401 }
402 
403 #ifdef MULTIPROCESSOR
404 /*
405  * On i386, the APs have not been hatched at the time hibernate resume is
406  * called, so there is no need to quiesce them. We do want to make sure
407  * however that we are on the BSP.
408  */
409 void
hibernate_quiesce_cpus(void)410 hibernate_quiesce_cpus(void)
411 {
412 	KASSERT(CPU_IS_PRIMARY(curcpu()));
413 }
414 #endif /* MULTIPROCESSOR */
415