18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci//
38c2ecf20Sopenharmony_ci// originally in linux/arch/arm/plat-s3c24xx/pm.c
48c2ecf20Sopenharmony_ci//
58c2ecf20Sopenharmony_ci// Copyright (c) 2004-2008 Simtec Electronics
68c2ecf20Sopenharmony_ci//	http://armlinux.simtec.co.uk
78c2ecf20Sopenharmony_ci//	Ben Dooks <ben@simtec.co.uk>
88c2ecf20Sopenharmony_ci//
98c2ecf20Sopenharmony_ci// S3C Power Mangament - suspend/resume memory corruption check.
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/suspend.h>
138c2ecf20Sopenharmony_ci#include <linux/init.h>
148c2ecf20Sopenharmony_ci#include <linux/crc32.h>
158c2ecf20Sopenharmony_ci#include <linux/ioport.h>
168c2ecf20Sopenharmony_ci#include <linux/slab.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#include <linux/soc/samsung/s3c-pm.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#if CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE < 1
218c2ecf20Sopenharmony_ci#error CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE must be a positive non-zero value
228c2ecf20Sopenharmony_ci#endif
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/* suspend checking code...
258c2ecf20Sopenharmony_ci *
268c2ecf20Sopenharmony_ci * this next area does a set of crc checks over all the installed
278c2ecf20Sopenharmony_ci * memory, so the system can verify if the resume was ok.
288c2ecf20Sopenharmony_ci *
298c2ecf20Sopenharmony_ci * CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE defines the block-size for the CRC,
308c2ecf20Sopenharmony_ci * increasing it will mean that the area corrupted will be less easy to spot,
318c2ecf20Sopenharmony_ci * and reducing the size will cause the CRC save area to grow
328c2ecf20Sopenharmony_ci*/
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#define CHECK_CHUNKSIZE (CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE * 1024)
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic u32 crc_size;	/* size needed for the crc block */
378c2ecf20Sopenharmony_cistatic u32 *crcs;	/* allocated over suspend/resume */
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_citypedef u32 *(run_fn_t)(struct resource *ptr, u32 *arg);
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci/* s3c_pm_run_res
428c2ecf20Sopenharmony_ci *
438c2ecf20Sopenharmony_ci * go through the given resource list, and look for system ram
448c2ecf20Sopenharmony_ci*/
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic void s3c_pm_run_res(struct resource *ptr, run_fn_t fn, u32 *arg)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	while (ptr != NULL) {
498c2ecf20Sopenharmony_ci		if (ptr->child != NULL)
508c2ecf20Sopenharmony_ci			s3c_pm_run_res(ptr->child, fn, arg);
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci		if ((ptr->flags & IORESOURCE_SYSTEM_RAM)
538c2ecf20Sopenharmony_ci				== IORESOURCE_SYSTEM_RAM) {
548c2ecf20Sopenharmony_ci			S3C_PMDBG("Found system RAM at %08lx..%08lx\n",
558c2ecf20Sopenharmony_ci				  (unsigned long)ptr->start,
568c2ecf20Sopenharmony_ci				  (unsigned long)ptr->end);
578c2ecf20Sopenharmony_ci			arg = (fn)(ptr, arg);
588c2ecf20Sopenharmony_ci		}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci		ptr = ptr->sibling;
618c2ecf20Sopenharmony_ci	}
628c2ecf20Sopenharmony_ci}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic void s3c_pm_run_sysram(run_fn_t fn, u32 *arg)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	s3c_pm_run_res(&iomem_resource, fn, arg);
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic u32 *s3c_pm_countram(struct resource *res, u32 *val)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	u32 size = (u32)resource_size(res);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	size += CHECK_CHUNKSIZE-1;
748c2ecf20Sopenharmony_ci	size /= CHECK_CHUNKSIZE;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	S3C_PMDBG("Area %08lx..%08lx, %d blocks\n",
778c2ecf20Sopenharmony_ci		  (unsigned long)res->start, (unsigned long)res->end, size);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	*val += size * sizeof(u32);
808c2ecf20Sopenharmony_ci	return val;
818c2ecf20Sopenharmony_ci}
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci/* s3c_pm_prepare_check
848c2ecf20Sopenharmony_ci *
858c2ecf20Sopenharmony_ci * prepare the necessary information for creating the CRCs. This
868c2ecf20Sopenharmony_ci * must be done before the final save, as it will require memory
878c2ecf20Sopenharmony_ci * allocating, and thus touching bits of the kernel we do not
888c2ecf20Sopenharmony_ci * know about.
898c2ecf20Sopenharmony_ci*/
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_civoid s3c_pm_check_prepare(void)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	crc_size = 0;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	s3c_pm_run_sysram(s3c_pm_countram, &crc_size);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	S3C_PMDBG("s3c_pm_prepare_check: %u checks needed\n", crc_size);
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	crcs = kmalloc(crc_size+4, GFP_KERNEL);
1008c2ecf20Sopenharmony_ci	if (crcs == NULL)
1018c2ecf20Sopenharmony_ci		printk(KERN_ERR "Cannot allocated CRC save area\n");
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_cistatic u32 *s3c_pm_makecheck(struct resource *res, u32 *val)
1058c2ecf20Sopenharmony_ci{
1068c2ecf20Sopenharmony_ci	unsigned long addr, left;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	for (addr = res->start; addr < res->end;
1098c2ecf20Sopenharmony_ci	     addr += CHECK_CHUNKSIZE) {
1108c2ecf20Sopenharmony_ci		left = res->end - addr;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci		if (left > CHECK_CHUNKSIZE)
1138c2ecf20Sopenharmony_ci			left = CHECK_CHUNKSIZE;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci		*val = crc32_le(~0, phys_to_virt(addr), left);
1168c2ecf20Sopenharmony_ci		val++;
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	return val;
1208c2ecf20Sopenharmony_ci}
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci/* s3c_pm_check_store
1238c2ecf20Sopenharmony_ci *
1248c2ecf20Sopenharmony_ci * compute the CRC values for the memory blocks before the final
1258c2ecf20Sopenharmony_ci * sleep.
1268c2ecf20Sopenharmony_ci*/
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_civoid s3c_pm_check_store(void)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	if (crcs != NULL)
1318c2ecf20Sopenharmony_ci		s3c_pm_run_sysram(s3c_pm_makecheck, crcs);
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci/* in_region
1358c2ecf20Sopenharmony_ci *
1368c2ecf20Sopenharmony_ci * return TRUE if the area defined by ptr..ptr+size contains the
1378c2ecf20Sopenharmony_ci * what..what+whatsz
1388c2ecf20Sopenharmony_ci*/
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cistatic inline int in_region(void *ptr, int size, void *what, size_t whatsz)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	if ((what+whatsz) < ptr)
1438c2ecf20Sopenharmony_ci		return 0;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	if (what > (ptr+size))
1468c2ecf20Sopenharmony_ci		return 0;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	return 1;
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci/**
1528c2ecf20Sopenharmony_ci * s3c_pm_runcheck() - helper to check a resource on restore.
1538c2ecf20Sopenharmony_ci * @res: The resource to check
1548c2ecf20Sopenharmony_ci * @vak: Pointer to list of CRC32 values to check.
1558c2ecf20Sopenharmony_ci *
1568c2ecf20Sopenharmony_ci * Called from the s3c_pm_check_restore() via s3c_pm_run_sysram(), this
1578c2ecf20Sopenharmony_ci * function runs the given memory resource checking it against the stored
1588c2ecf20Sopenharmony_ci * CRC to ensure that memory is restored. The function tries to skip as
1598c2ecf20Sopenharmony_ci * many of the areas used during the suspend process.
1608c2ecf20Sopenharmony_ci */
1618c2ecf20Sopenharmony_cistatic u32 *s3c_pm_runcheck(struct resource *res, u32 *val)
1628c2ecf20Sopenharmony_ci{
1638c2ecf20Sopenharmony_ci	unsigned long addr;
1648c2ecf20Sopenharmony_ci	unsigned long left;
1658c2ecf20Sopenharmony_ci	void *stkpage;
1668c2ecf20Sopenharmony_ci	void *ptr;
1678c2ecf20Sopenharmony_ci	u32 calc;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	stkpage = (void *)((u32)&calc & ~PAGE_MASK);
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	for (addr = res->start; addr < res->end;
1728c2ecf20Sopenharmony_ci	     addr += CHECK_CHUNKSIZE) {
1738c2ecf20Sopenharmony_ci		left = res->end - addr;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci		if (left > CHECK_CHUNKSIZE)
1768c2ecf20Sopenharmony_ci			left = CHECK_CHUNKSIZE;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci		ptr = phys_to_virt(addr);
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci		if (in_region(ptr, left, stkpage, 4096)) {
1818c2ecf20Sopenharmony_ci			S3C_PMDBG("skipping %08lx, has stack in\n", addr);
1828c2ecf20Sopenharmony_ci			goto skip_check;
1838c2ecf20Sopenharmony_ci		}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci		if (in_region(ptr, left, crcs, crc_size)) {
1868c2ecf20Sopenharmony_ci			S3C_PMDBG("skipping %08lx, has crc block in\n", addr);
1878c2ecf20Sopenharmony_ci			goto skip_check;
1888c2ecf20Sopenharmony_ci		}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci		/* calculate and check the checksum */
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci		calc = crc32_le(~0, ptr, left);
1938c2ecf20Sopenharmony_ci		if (calc != *val) {
1948c2ecf20Sopenharmony_ci			printk(KERN_ERR "Restore CRC error at "
1958c2ecf20Sopenharmony_ci			       "%08lx (%08x vs %08x)\n", addr, calc, *val);
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci			S3C_PMDBG("Restore CRC error at %08lx (%08x vs %08x)\n",
1988c2ecf20Sopenharmony_ci			    addr, calc, *val);
1998c2ecf20Sopenharmony_ci		}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	skip_check:
2028c2ecf20Sopenharmony_ci		val++;
2038c2ecf20Sopenharmony_ci	}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	return val;
2068c2ecf20Sopenharmony_ci}
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci/**
2098c2ecf20Sopenharmony_ci * s3c_pm_check_restore() - memory check called on resume
2108c2ecf20Sopenharmony_ci *
2118c2ecf20Sopenharmony_ci * check the CRCs after the restore event and free the memory used
2128c2ecf20Sopenharmony_ci * to hold them
2138c2ecf20Sopenharmony_ci*/
2148c2ecf20Sopenharmony_civoid s3c_pm_check_restore(void)
2158c2ecf20Sopenharmony_ci{
2168c2ecf20Sopenharmony_ci	if (crcs != NULL)
2178c2ecf20Sopenharmony_ci		s3c_pm_run_sysram(s3c_pm_runcheck, crcs);
2188c2ecf20Sopenharmony_ci}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci/**
2218c2ecf20Sopenharmony_ci * s3c_pm_check_cleanup() - free memory resources
2228c2ecf20Sopenharmony_ci *
2238c2ecf20Sopenharmony_ci * Free the resources that where allocated by the suspend
2248c2ecf20Sopenharmony_ci * memory check code. We do this separately from the
2258c2ecf20Sopenharmony_ci * s3c_pm_check_restore() function as we cannot call any
2268c2ecf20Sopenharmony_ci * functions that might sleep during that resume.
2278c2ecf20Sopenharmony_ci */
2288c2ecf20Sopenharmony_civoid s3c_pm_check_cleanup(void)
2298c2ecf20Sopenharmony_ci{
2308c2ecf20Sopenharmony_ci	kfree(crcs);
2318c2ecf20Sopenharmony_ci	crcs = NULL;
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ci
234