diff options
Diffstat (limited to 'arch/arm64/kernel/hibernate.c')
| -rw-r--r-- | arch/arm64/kernel/hibernate.c | 151 | 
1 files changed, 80 insertions, 71 deletions
diff --git a/arch/arm64/kernel/hibernate.c b/arch/arm64/kernel/hibernate.c index a96b2921d22c..590963c9c609 100644 --- a/arch/arm64/kernel/hibernate.c +++ b/arch/arm64/kernel/hibernate.c @@ -182,78 +182,79 @@ int arch_hibernation_header_restore(void *addr)  }  EXPORT_SYMBOL(arch_hibernation_header_restore); -/* - * Copies length bytes, starting at src_start into an new page, - * perform cache maintentance, then maps it at the specified address low - * address as executable. - * - * This is used by hibernate to copy the code it needs to execute when - * overwriting the kernel text. This function generates a new set of page - * tables, which it loads into ttbr0. - * - * Length is provided as we probably only want 4K of data, even on a 64K - * page system. - */ -static int create_safe_exec_page(void *src_start, size_t length, -				 unsigned long dst_addr, -				 phys_addr_t *phys_dst_addr, -				 void *(*allocator)(gfp_t mask), -				 gfp_t mask) +static int trans_pgd_map_page(pgd_t *trans_pgd, void *page, +		       unsigned long dst_addr, +		       pgprot_t pgprot)  { -	int rc = 0; -	pgd_t *trans_pgd;  	pgd_t *pgdp;  	pud_t *pudp;  	pmd_t *pmdp;  	pte_t *ptep; -	unsigned long dst = (unsigned long)allocator(mask); - -	if (!dst) { -		rc = -ENOMEM; -		goto out; -	} - -	memcpy((void *)dst, src_start, length); -	__flush_icache_range(dst, dst + length); - -	trans_pgd = allocator(mask); -	if (!trans_pgd) { -		rc = -ENOMEM; -		goto out; -	}  	pgdp = pgd_offset_raw(trans_pgd, dst_addr);  	if (pgd_none(READ_ONCE(*pgdp))) { -		pudp = allocator(mask); -		if (!pudp) { -			rc = -ENOMEM; -			goto out; -		} +		pudp = (void *)get_safe_page(GFP_ATOMIC); +		if (!pudp) +			return -ENOMEM;  		pgd_populate(&init_mm, pgdp, pudp);  	}  	pudp = pud_offset(pgdp, dst_addr);  	if (pud_none(READ_ONCE(*pudp))) { -		pmdp = allocator(mask); -		if (!pmdp) { -			rc = -ENOMEM; -			goto out; -		} +		pmdp = (void *)get_safe_page(GFP_ATOMIC); +		if (!pmdp) +			return -ENOMEM;  		pud_populate(&init_mm, pudp, pmdp);  	}  	pmdp = pmd_offset(pudp, dst_addr);  	if (pmd_none(READ_ONCE(*pmdp))) { -		ptep = allocator(mask); -		if (!ptep) { -			rc = -ENOMEM; -			goto out; -		} +		ptep = (void *)get_safe_page(GFP_ATOMIC); +		if (!ptep) +			return -ENOMEM;  		pmd_populate_kernel(&init_mm, pmdp, ptep);  	}  	ptep = pte_offset_kernel(pmdp, dst_addr); -	set_pte(ptep, pfn_pte(virt_to_pfn(dst), PAGE_KERNEL_EXEC)); +	set_pte(ptep, pfn_pte(virt_to_pfn(page), PAGE_KERNEL_EXEC)); + +	return 0; +} + +/* + * Copies length bytes, starting at src_start into an new page, + * perform cache maintenance, then maps it at the specified address low + * address as executable. + * + * This is used by hibernate to copy the code it needs to execute when + * overwriting the kernel text. This function generates a new set of page + * tables, which it loads into ttbr0. + * + * Length is provided as we probably only want 4K of data, even on a 64K + * page system. + */ +static int create_safe_exec_page(void *src_start, size_t length, +				 unsigned long dst_addr, +				 phys_addr_t *phys_dst_addr) +{ +	void *page = (void *)get_safe_page(GFP_ATOMIC); +	pgd_t *trans_pgd; +	int rc; + +	if (!page) +		return -ENOMEM; + +	memcpy(page, src_start, length); +	__flush_icache_range((unsigned long)page, (unsigned long)page + length); + +	trans_pgd = (void *)get_safe_page(GFP_ATOMIC); +	if (!trans_pgd) +		return -ENOMEM; + +	rc = trans_pgd_map_page(trans_pgd, page, dst_addr, +				PAGE_KERNEL_EXEC); +	if (rc) +		return rc;  	/*  	 * Load our new page tables. A strict BBM approach requires that we @@ -269,13 +270,12 @@ static int create_safe_exec_page(void *src_start, size_t length,  	 */  	cpu_set_reserved_ttbr0();  	local_flush_tlb_all(); -	write_sysreg(phys_to_ttbr(virt_to_phys(pgdp)), ttbr0_el1); +	write_sysreg(phys_to_ttbr(virt_to_phys(trans_pgd)), ttbr0_el1);  	isb(); -	*phys_dst_addr = virt_to_phys((void *)dst); +	*phys_dst_addr = virt_to_phys(page); -out: -	return rc; +	return 0;  }  #define dcache_clean_range(start, end)	__flush_dcache_area(start, (end - start)) @@ -450,7 +450,7 @@ static int copy_pud(pgd_t *dst_pgdp, pgd_t *src_pgdp, unsigned long start,  				return -ENOMEM;  		} else {  			set_pud(dst_pudp, -				__pud(pud_val(pud) & ~PMD_SECT_RDONLY)); +				__pud(pud_val(pud) & ~PUD_SECT_RDONLY));  		}  	} while (dst_pudp++, src_pudp++, addr = next, addr != end); @@ -476,6 +476,24 @@ static int copy_page_tables(pgd_t *dst_pgdp, unsigned long start,  	return 0;  } +static int trans_pgd_create_copy(pgd_t **dst_pgdp, unsigned long start, +			  unsigned long end) +{ +	int rc; +	pgd_t *trans_pgd = (pgd_t *)get_safe_page(GFP_ATOMIC); + +	if (!trans_pgd) { +		pr_err("Failed to allocate memory for temporary page tables.\n"); +		return -ENOMEM; +	} + +	rc = copy_page_tables(trans_pgd, start, end); +	if (!rc) +		*dst_pgdp = trans_pgd; + +	return rc; +} +  /*   * Setup then Resume from the hibernate image using swsusp_arch_suspend_exit().   * @@ -484,7 +502,7 @@ static int copy_page_tables(pgd_t *dst_pgdp, unsigned long start,   */  int swsusp_arch_resume(void)  { -	int rc = 0; +	int rc;  	void *zero_page;  	size_t exit_size;  	pgd_t *tmp_pg_dir; @@ -497,15 +515,9 @@ int swsusp_arch_resume(void)  	 * Create a second copy of just the linear map, and use this when  	 * restoring.  	 */ -	tmp_pg_dir = (pgd_t *)get_safe_page(GFP_ATOMIC); -	if (!tmp_pg_dir) { -		pr_err("Failed to allocate memory for temporary page tables.\n"); -		rc = -ENOMEM; -		goto out; -	} -	rc = copy_page_tables(tmp_pg_dir, PAGE_OFFSET, PAGE_END); +	rc = trans_pgd_create_copy(&tmp_pg_dir, PAGE_OFFSET, PAGE_END);  	if (rc) -		goto out; +		return rc;  	/*  	 * We need a zero page that is zero before & after resume in order to @@ -514,8 +526,7 @@ int swsusp_arch_resume(void)  	zero_page = (void *)get_safe_page(GFP_ATOMIC);  	if (!zero_page) {  		pr_err("Failed to allocate zero page.\n"); -		rc = -ENOMEM; -		goto out; +		return -ENOMEM;  	}  	/* @@ -530,11 +541,10 @@ int swsusp_arch_resume(void)  	 */  	rc = create_safe_exec_page(__hibernate_exit_text_start, exit_size,  				   (unsigned long)hibernate_exit, -				   &phys_hibernate_exit, -				   (void *)get_safe_page, GFP_ATOMIC); +				   &phys_hibernate_exit);  	if (rc) {  		pr_err("Failed to create safe executable page for hibernate_exit code.\n"); -		goto out; +		return rc;  	}  	/* @@ -561,8 +571,7 @@ int swsusp_arch_resume(void)  		       resume_hdr.reenter_kernel, restore_pblist,  		       resume_hdr.__hyp_stub_vectors, virt_to_phys(zero_page)); -out: -	return rc; +	return 0;  }  int hibernate_resume_nonboot_cpu_disable(void)  | 

