xref: /openbmc/linux/drivers/soc/samsung/s3c-pm-check.c (revision cdd38c5f1ce4398ec58fec95904b75824daab7b5)
117132da7SArnd Bergmann // SPDX-License-Identifier: GPL-2.0
217132da7SArnd Bergmann //
317132da7SArnd Bergmann // originally in linux/arch/arm/plat-s3c24xx/pm.c
417132da7SArnd Bergmann //
517132da7SArnd Bergmann // Copyright (c) 2004-2008 Simtec Electronics
617132da7SArnd Bergmann //	http://armlinux.simtec.co.uk
717132da7SArnd Bergmann //	Ben Dooks <ben@simtec.co.uk>
817132da7SArnd Bergmann //
917132da7SArnd Bergmann // S3C Power Mangament - suspend/resume memory corruption check.
1017132da7SArnd Bergmann 
1117132da7SArnd Bergmann #include <linux/kernel.h>
1217132da7SArnd Bergmann #include <linux/suspend.h>
1317132da7SArnd Bergmann #include <linux/init.h>
1417132da7SArnd Bergmann #include <linux/crc32.h>
1517132da7SArnd Bergmann #include <linux/ioport.h>
1617132da7SArnd Bergmann #include <linux/slab.h>
1717132da7SArnd Bergmann 
1817132da7SArnd Bergmann #include <linux/soc/samsung/s3c-pm.h>
1917132da7SArnd Bergmann 
2017132da7SArnd Bergmann #if CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE < 1
2117132da7SArnd Bergmann #error CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE must be a positive non-zero value
2217132da7SArnd Bergmann #endif
2317132da7SArnd Bergmann 
2417132da7SArnd Bergmann /* suspend checking code...
2517132da7SArnd Bergmann  *
2617132da7SArnd Bergmann  * this next area does a set of crc checks over all the installed
2717132da7SArnd Bergmann  * memory, so the system can verify if the resume was ok.
2817132da7SArnd Bergmann  *
2917132da7SArnd Bergmann  * CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE defines the block-size for the CRC,
3017132da7SArnd Bergmann  * increasing it will mean that the area corrupted will be less easy to spot,
3117132da7SArnd Bergmann  * and reducing the size will cause the CRC save area to grow
3217132da7SArnd Bergmann */
3317132da7SArnd Bergmann 
3417132da7SArnd Bergmann #define CHECK_CHUNKSIZE (CONFIG_SAMSUNG_PM_CHECK_CHUNKSIZE * 1024)
3517132da7SArnd Bergmann 
3617132da7SArnd Bergmann static u32 crc_size;	/* size needed for the crc block */
3717132da7SArnd Bergmann static u32 *crcs;	/* allocated over suspend/resume */
3817132da7SArnd Bergmann 
3917132da7SArnd Bergmann typedef u32 *(run_fn_t)(struct resource *ptr, u32 *arg);
4017132da7SArnd Bergmann 
4117132da7SArnd Bergmann /* s3c_pm_run_res
4217132da7SArnd Bergmann  *
4317132da7SArnd Bergmann  * go through the given resource list, and look for system ram
4417132da7SArnd Bergmann */
4517132da7SArnd Bergmann 
s3c_pm_run_res(struct resource * ptr,run_fn_t fn,u32 * arg)4617132da7SArnd Bergmann static void s3c_pm_run_res(struct resource *ptr, run_fn_t fn, u32 *arg)
4717132da7SArnd Bergmann {
4817132da7SArnd Bergmann 	while (ptr != NULL) {
4917132da7SArnd Bergmann 		if (ptr->child != NULL)
5017132da7SArnd Bergmann 			s3c_pm_run_res(ptr->child, fn, arg);
5117132da7SArnd Bergmann 
5217132da7SArnd Bergmann 		if ((ptr->flags & IORESOURCE_SYSTEM_RAM)
5317132da7SArnd Bergmann 				== IORESOURCE_SYSTEM_RAM) {
5417132da7SArnd Bergmann 			S3C_PMDBG("Found system RAM at %08lx..%08lx\n",
5517132da7SArnd Bergmann 				  (unsigned long)ptr->start,
5617132da7SArnd Bergmann 				  (unsigned long)ptr->end);
5717132da7SArnd Bergmann 			arg = (fn)(ptr, arg);
5817132da7SArnd Bergmann 		}
5917132da7SArnd Bergmann 
6017132da7SArnd Bergmann 		ptr = ptr->sibling;
6117132da7SArnd Bergmann 	}
6217132da7SArnd Bergmann }
6317132da7SArnd Bergmann 
s3c_pm_run_sysram(run_fn_t fn,u32 * arg)6417132da7SArnd Bergmann static void s3c_pm_run_sysram(run_fn_t fn, u32 *arg)
6517132da7SArnd Bergmann {
6617132da7SArnd Bergmann 	s3c_pm_run_res(&iomem_resource, fn, arg);
6717132da7SArnd Bergmann }
6817132da7SArnd Bergmann 
s3c_pm_countram(struct resource * res,u32 * val)6917132da7SArnd Bergmann static u32 *s3c_pm_countram(struct resource *res, u32 *val)
7017132da7SArnd Bergmann {
7117132da7SArnd Bergmann 	u32 size = (u32)resource_size(res);
7217132da7SArnd Bergmann 
7317132da7SArnd Bergmann 	size += CHECK_CHUNKSIZE-1;
7417132da7SArnd Bergmann 	size /= CHECK_CHUNKSIZE;
7517132da7SArnd Bergmann 
7617132da7SArnd Bergmann 	S3C_PMDBG("Area %08lx..%08lx, %d blocks\n",
7717132da7SArnd Bergmann 		  (unsigned long)res->start, (unsigned long)res->end, size);
7817132da7SArnd Bergmann 
7917132da7SArnd Bergmann 	*val += size * sizeof(u32);
8017132da7SArnd Bergmann 	return val;
8117132da7SArnd Bergmann }
8217132da7SArnd Bergmann 
8317132da7SArnd Bergmann /* s3c_pm_prepare_check
8417132da7SArnd Bergmann  *
8517132da7SArnd Bergmann  * prepare the necessary information for creating the CRCs. This
8617132da7SArnd Bergmann  * must be done before the final save, as it will require memory
8717132da7SArnd Bergmann  * allocating, and thus touching bits of the kernel we do not
8817132da7SArnd Bergmann  * know about.
8917132da7SArnd Bergmann */
9017132da7SArnd Bergmann 
s3c_pm_check_prepare(void)9117132da7SArnd Bergmann void s3c_pm_check_prepare(void)
9217132da7SArnd Bergmann {
9317132da7SArnd Bergmann 	crc_size = 0;
9417132da7SArnd Bergmann 
9517132da7SArnd Bergmann 	s3c_pm_run_sysram(s3c_pm_countram, &crc_size);
9617132da7SArnd Bergmann 
9717132da7SArnd Bergmann 	S3C_PMDBG("s3c_pm_prepare_check: %u checks needed\n", crc_size);
9817132da7SArnd Bergmann 
9917132da7SArnd Bergmann 	crcs = kmalloc(crc_size+4, GFP_KERNEL);
10017132da7SArnd Bergmann 	if (crcs == NULL)
10117132da7SArnd Bergmann 		printk(KERN_ERR "Cannot allocated CRC save area\n");
10217132da7SArnd Bergmann }
10317132da7SArnd Bergmann 
s3c_pm_makecheck(struct resource * res,u32 * val)10417132da7SArnd Bergmann static u32 *s3c_pm_makecheck(struct resource *res, u32 *val)
10517132da7SArnd Bergmann {
10617132da7SArnd Bergmann 	unsigned long addr, left;
10717132da7SArnd Bergmann 
10817132da7SArnd Bergmann 	for (addr = res->start; addr < res->end;
10917132da7SArnd Bergmann 	     addr += CHECK_CHUNKSIZE) {
11017132da7SArnd Bergmann 		left = res->end - addr;
11117132da7SArnd Bergmann 
11217132da7SArnd Bergmann 		if (left > CHECK_CHUNKSIZE)
11317132da7SArnd Bergmann 			left = CHECK_CHUNKSIZE;
11417132da7SArnd Bergmann 
11517132da7SArnd Bergmann 		*val = crc32_le(~0, phys_to_virt(addr), left);
11617132da7SArnd Bergmann 		val++;
11717132da7SArnd Bergmann 	}
11817132da7SArnd Bergmann 
11917132da7SArnd Bergmann 	return val;
12017132da7SArnd Bergmann }
12117132da7SArnd Bergmann 
12217132da7SArnd Bergmann /* s3c_pm_check_store
12317132da7SArnd Bergmann  *
12417132da7SArnd Bergmann  * compute the CRC values for the memory blocks before the final
12517132da7SArnd Bergmann  * sleep.
12617132da7SArnd Bergmann */
12717132da7SArnd Bergmann 
s3c_pm_check_store(void)12817132da7SArnd Bergmann void s3c_pm_check_store(void)
12917132da7SArnd Bergmann {
13017132da7SArnd Bergmann 	if (crcs != NULL)
13117132da7SArnd Bergmann 		s3c_pm_run_sysram(s3c_pm_makecheck, crcs);
13217132da7SArnd Bergmann }
13317132da7SArnd Bergmann 
13417132da7SArnd Bergmann /* in_region
13517132da7SArnd Bergmann  *
13617132da7SArnd Bergmann  * return TRUE if the area defined by ptr..ptr+size contains the
13717132da7SArnd Bergmann  * what..what+whatsz
13817132da7SArnd Bergmann */
13917132da7SArnd Bergmann 
in_region(void * ptr,int size,void * what,size_t whatsz)14017132da7SArnd Bergmann static inline int in_region(void *ptr, int size, void *what, size_t whatsz)
14117132da7SArnd Bergmann {
14217132da7SArnd Bergmann 	if ((what+whatsz) < ptr)
14317132da7SArnd Bergmann 		return 0;
14417132da7SArnd Bergmann 
14517132da7SArnd Bergmann 	if (what > (ptr+size))
14617132da7SArnd Bergmann 		return 0;
14717132da7SArnd Bergmann 
14817132da7SArnd Bergmann 	return 1;
14917132da7SArnd Bergmann }
15017132da7SArnd Bergmann 
15117132da7SArnd Bergmann /**
15217132da7SArnd Bergmann  * s3c_pm_runcheck() - helper to check a resource on restore.
15317132da7SArnd Bergmann  * @res: The resource to check
154*4d05446aSLee Jones  * @val: Pointer to list of CRC32 values to check.
15517132da7SArnd Bergmann  *
15617132da7SArnd Bergmann  * Called from the s3c_pm_check_restore() via s3c_pm_run_sysram(), this
15717132da7SArnd Bergmann  * function runs the given memory resource checking it against the stored
15817132da7SArnd Bergmann  * CRC to ensure that memory is restored. The function tries to skip as
15917132da7SArnd Bergmann  * many of the areas used during the suspend process.
16017132da7SArnd Bergmann  */
s3c_pm_runcheck(struct resource * res,u32 * val)16117132da7SArnd Bergmann static u32 *s3c_pm_runcheck(struct resource *res, u32 *val)
16217132da7SArnd Bergmann {
16317132da7SArnd Bergmann 	unsigned long addr;
16417132da7SArnd Bergmann 	unsigned long left;
16517132da7SArnd Bergmann 	void *stkpage;
16617132da7SArnd Bergmann 	void *ptr;
16717132da7SArnd Bergmann 	u32 calc;
16817132da7SArnd Bergmann 
16917132da7SArnd Bergmann 	stkpage = (void *)((u32)&calc & ~PAGE_MASK);
17017132da7SArnd Bergmann 
17117132da7SArnd Bergmann 	for (addr = res->start; addr < res->end;
17217132da7SArnd Bergmann 	     addr += CHECK_CHUNKSIZE) {
17317132da7SArnd Bergmann 		left = res->end - addr;
17417132da7SArnd Bergmann 
17517132da7SArnd Bergmann 		if (left > CHECK_CHUNKSIZE)
17617132da7SArnd Bergmann 			left = CHECK_CHUNKSIZE;
17717132da7SArnd Bergmann 
17817132da7SArnd Bergmann 		ptr = phys_to_virt(addr);
17917132da7SArnd Bergmann 
18017132da7SArnd Bergmann 		if (in_region(ptr, left, stkpage, 4096)) {
18117132da7SArnd Bergmann 			S3C_PMDBG("skipping %08lx, has stack in\n", addr);
18217132da7SArnd Bergmann 			goto skip_check;
18317132da7SArnd Bergmann 		}
18417132da7SArnd Bergmann 
18517132da7SArnd Bergmann 		if (in_region(ptr, left, crcs, crc_size)) {
18617132da7SArnd Bergmann 			S3C_PMDBG("skipping %08lx, has crc block in\n", addr);
18717132da7SArnd Bergmann 			goto skip_check;
18817132da7SArnd Bergmann 		}
18917132da7SArnd Bergmann 
19017132da7SArnd Bergmann 		/* calculate and check the checksum */
19117132da7SArnd Bergmann 
19217132da7SArnd Bergmann 		calc = crc32_le(~0, ptr, left);
19317132da7SArnd Bergmann 		if (calc != *val) {
19417132da7SArnd Bergmann 			printk(KERN_ERR "Restore CRC error at "
19517132da7SArnd Bergmann 			       "%08lx (%08x vs %08x)\n", addr, calc, *val);
19617132da7SArnd Bergmann 
19717132da7SArnd Bergmann 			S3C_PMDBG("Restore CRC error at %08lx (%08x vs %08x)\n",
19817132da7SArnd Bergmann 			    addr, calc, *val);
19917132da7SArnd Bergmann 		}
20017132da7SArnd Bergmann 
20117132da7SArnd Bergmann 	skip_check:
20217132da7SArnd Bergmann 		val++;
20317132da7SArnd Bergmann 	}
20417132da7SArnd Bergmann 
20517132da7SArnd Bergmann 	return val;
20617132da7SArnd Bergmann }
20717132da7SArnd Bergmann 
20817132da7SArnd Bergmann /**
20917132da7SArnd Bergmann  * s3c_pm_check_restore() - memory check called on resume
21017132da7SArnd Bergmann  *
21117132da7SArnd Bergmann  * check the CRCs after the restore event and free the memory used
21217132da7SArnd Bergmann  * to hold them
21317132da7SArnd Bergmann */
s3c_pm_check_restore(void)21417132da7SArnd Bergmann void s3c_pm_check_restore(void)
21517132da7SArnd Bergmann {
21617132da7SArnd Bergmann 	if (crcs != NULL)
21717132da7SArnd Bergmann 		s3c_pm_run_sysram(s3c_pm_runcheck, crcs);
21817132da7SArnd Bergmann }
21917132da7SArnd Bergmann 
22017132da7SArnd Bergmann /**
22117132da7SArnd Bergmann  * s3c_pm_check_cleanup() - free memory resources
22217132da7SArnd Bergmann  *
22317132da7SArnd Bergmann  * Free the resources that where allocated by the suspend
22417132da7SArnd Bergmann  * memory check code. We do this separately from the
22517132da7SArnd Bergmann  * s3c_pm_check_restore() function as we cannot call any
22617132da7SArnd Bergmann  * functions that might sleep during that resume.
22717132da7SArnd Bergmann  */
s3c_pm_check_cleanup(void)22817132da7SArnd Bergmann void s3c_pm_check_cleanup(void)
22917132da7SArnd Bergmann {
23017132da7SArnd Bergmann 	kfree(crcs);
23117132da7SArnd Bergmann 	crcs = NULL;
23217132da7SArnd Bergmann }
23317132da7SArnd Bergmann 
234